Introduction to F#

Mikhail Smal https://mikhail.dev Win a ticket to Update Conference 2019!

https://forms.gle/Z9EBCmSG877BBQMg7 Introduction to F#

Mikhail Smal https://mikhail.dev

F#

▪ Multiparadigm ▪ Functional first ▪ Strongly and statically typed ▪ Open-source ▪ Cross-platform thanks to .NET Core ▪ Compiles to JavaScript thanks to Fable ▪ Scripting language ▪ Awesome community  Where to use

▪ Web development with SAFE ▪ Saturn – web-server framework ▪ Azure – cloud provider ▪ Fable – F# -> JS compiler ▪ Elmish – Model-View-Update implementation ▪ Serverless (FaaS) ▪ Mobile development (, React Native) Tooling

▪ Visual Studio ▪ Visual Studio Code (Ionide extension) ▪ JetBrains Rider ▪ MonoDevelop ▪ -fsharp Basic syntax Values

// int let age = 30 // inferred

// int let age : int = 31 // explicit

// string let age = string age // totally different type

// unit let nothing = () Functions

// 'a -> 'a -> 'a let add x y = x + y // indentation instead of curly brackets

// int -> int let addTen = add 10 // partial application

// int let result = addTen 2 // 12

// ('a * 'b) -> ('a -> 'b -> ') -> 'c let exec (x, y) f = f x y let result1 = exec (100, 10) (-) // 90 let result2 = exec ("Hello", "developers") (sprintf "%s, %s!") // ”Hello, developers!” let (>>!) = exec let result3 = ("Hello", "devs") >>! (sprintf "%s, %s!") let result4 = ("Hello", "devs") ||> (sprintf "%s, %s!") Functions with types let add (x : int) (y : int) : int = x + y let printAll<'a> (collection : 'a seq) = Seq.iter (fun x -> printfn "%A" x) collection printAll [1;2;3] type AddFunction = int -> int -> int let add : AddFunction = fun x y -> x + y Records (Product types) type User = { FirstName : string LastName : string Age : int } let user = { FirstName = "Mikhail" LastName = "Smal" Age = 30 } user.Age = 31 // = - comparison operator. Result is false let olderUser = { user with Age = 31 } type UserWithMutableAge = { FirstName : string LastName : string mutable Age : int } user.Age <- 31 Anonymous records let data = {| X = 1; Y = 2 |} let data' = {| data with Y = 3 |} let data = {| X = 1; Y = 2 |} let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |} type R = { X: int } let data = { X = 1 } let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |} Discriminated unions (Sum types) type CardType = | Visa | Mastercard type CardNumber = CardNumber of string // Single case DU type CheckNumber = CheckNumber of int type PaymentMethod = | Cash | Check of CheckNumber | Card of CardType * CardNumber let checkNumber = CheckNumber 999 let cashPaymentMethod = Cash let checkPaymentMethod = Check checkNumber let checkPaymentMethod = Check (CheckNumber 999) let cardPaymentMethod = Card (Visa, CardNumber "XXXX-XXXX-XXXX-XXXX") F# is awesome for DDD Naming

let ``Function throws an exception when receives ¯\_(ツ)_/¯`` () = ... type [] g type [] inch type Gramms = Gramms of int type Inches = Inches of float type GadgetName = GadgetName of string

// 5 characters starting with T type PhoneCode = PhoneCode of string // 10000 < code < 100000 type TabletCode = TabletCode of int type GadgetCode = | PhoneCode of PhoneCode | TabletCode of TabletCode type Gadget = { Code : GadgetCode Name : GadgetName Weight : Gramms ScreenSize : Inches } Restrictions

// 5 characters starting with T type PhoneCode = private PhoneCode of string let phoneCode = PhoneCode "T1234" // Won't compile let (PhoneCode code) = phoneCode // Won't work in other modules type Option<'a> = | Some of 'a | None module PhoneCode = let create (s : string) = if not (isNull s) && s.StartsWith("T") && s.Length = 5 then Some (PhoneCode s) else None

let value (PhoneCode code) = // Works only in one module code Pattern matching open FSharp.Data.UnitSystems.SI.UnitSymbols type Direction = North | South | East | West type Weather = | Cold of temperature:float | Sunny | Wet | Windy of Direction * windspeed:float let (|Low|Medium|High|) speed = // Active patterns if speed > 10. then High elif speed > 5. then Medium else Low match weather with | Cold temp when temp < 2.0 -> "Really cold!" | Cold _ | Wet -> "Miserable weather!" | Sunny -> "Nice weather" | Windy (North, High) -> "High speed northernly wind!" | Windy (South, _) -> "Blowing southwards" | Windy _ -> "It's windy!" Collections let listOfInts1 = [1;2;3] let listOfInts2 = [ "Line1" "Line2" "Line3" ] let listOfInts3 = [1..10] let arrayOfInts = [|1;2;3|] // Cannot be matched match listOfInts1 with | [] -> 0 | [1;_] -> 1 | head::tail -> head | _ -> 100 Composition

// int -> float let foo x = ...

// float -> string let bar x = ...

// int -> string let fooBar = foo >> bar Pipelines let data = [(true, 100); (true, 200); (false, -10)] data |> List.filter fst |> List.sortByDescending snd |> List.take 1 |> List.map string |> List.head Computation expressions let squares = seq { for i in 1..10 do yield i * i } let ints = seq { yield! [1;2;3] } Computation expressions let option1 = Some 1 let option2 = Some 2 let result = let result = optional { option1 let! value1 = option1 |> Option.bind (fun value1 -> let! value2 = option2 option2 return value1 + value2 |> } Option.bind (fun value2 -> Some (value1 + value2) ) ) Saturn CEs let app = application { pipe_through endpointPipe

use_router topRouter url "http://0.0.0.0:8085/" memory_cache use_static "static" use_gzip } let ctrl = controller { index someIndexHandler add someAddHandler show someShowHandler edit someEditHandler } Railway-oriented programming Imperative code string ExecuteUseCase() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.UpdateDbFromRequest(request); smtpServer.SendEmail(request.Email); return "Success"; } Functional flow let executeUseCase = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.UpdateDbFromRequest(request); smtpServer.SendEmail(request.Email);

return "OK"; } Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid"; } canonicalizeEmail(request); db.UpdateDbFromRequest(request); smtpServer.SendEmail(request.Email);

return "OK"; } Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid"; } canonicalizeEmail(request); var result = db.UpdateDbFromRequest(request); if (!result) { return "Customer record not found"; } smtpServer.SendEmail(request.Email);

return "OK"; } Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid"; } canonicalizeEmail(request); try { var result = db.UpdateDbFromRequest(request); if (!result) { return "Customer record not found"; } } catch { return "DB error: Customer record not updated"; } smtpServer.SendEmail(request.Email);

return "OK"; } Imperative code with error handling string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid"; } canonicalizeEmail(request); try { var result = db.UpdateDbFromRequest(request); if (!result) { return "Customer record not found"; } } catch { return "DB error: Customer record not updated"; } if (!smtpServer.SendEmail(request.Email)) { log.Error "Customer email not sent" }

return "OK"; } Functional flow with error handling Before let executeUseCase = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage Functional flow with error handling After let updateCustomerWithErrorHandling = receiveRequest >> validateRequest >> canonicalizeEmail >> updateDbFromRequest >> sendEmail >> returnMessage Result type type Result<'Success,'Failure> = | Ok of 'Success | Error of 'Failure Validation function

Request Validate Success

Failure let validateInput input = if input.name = "" then Error "Name must not be blank" elif input.email = "" then Error "Email must not be blank" else Ok input // Success Switch Switches composition

Validate UpdateDb on success

bypass Switches composition

Validate UpdateDb Switches composition

Validate UpdateDb SendEmail Switches composition Switches composition One track composition Two-track composition Wrong composition Adapter block

let bind switchFunction = fun twoTrackInput -> match twoTrackInput with | Ok s -> switchFunction s | Error f -> Error f

val bind : ('a -> Result<'b,'c>) -> Result<'a,'c> -> Result<'b,'c> Bind example

nameNotBlank (combined with) name50 (combined with) emailNotBlank

// Input -> Result Bind example

bind nameNotBlank bind name50 bind emailNotBlank

// (bind nameNotBlank): Result -> Result Bind example bind nameNotBlank >> bind name50 >> bind emailNotBlank Bind example

let validateRequest = bind nameNotBlank >> bind name50 >> bind emailNotBlank

// validateRequest: Result -> Result Bind example let (>>=) twoTrackInput switchFunction = bind switchFunction twoTrackInput

let validateRequest twoTrackInput = twoTrackInput >>= nameNotBlank >>= name50 >>= emailNotBlank Further topics

▪ Single track functions ▪ Dead-end functions ▪ Functions that throw exceptions ▪ Supervisory function ▪ Parallel tracks Asynchronous programming .NET

▪ Thread ▪ AutoResetEvent ▪ BackgroundWorker ▪ IAsyncResult ▪ Task Async F#

▪ Async<‘T> type ▪ Async module ▪ async { } computation expression Async example

// string -> Async let printTotalFileBytes path = async { let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask let fileName = Path.GetFileName(path) printfn "File %s has % bytes" fileName bytes.Length } printTotalFileBytes "path-to-file.txt" |> Async.RunSynchronously F# async computation

▪ Must be explicitly started ▪ Easier to combine and sequence Async example let getTotalFileBytes path = async { let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask return bytes.Length } let total = arrayOfPaths |> Array.map getTotalFileBytes |> Async.Parallel |> Async.RunSynchronously |> Array.sum Asynchronous programming in F# IS NOT an abstraction for multi-threading Nested workflow let nestedWorkflow = async { printfn "Starting parent" let! childWorkflow = Async.StartChild sleepWorkflow

// give the child a chance and then keep working do! Async.Sleep 100 printfn "Doing something useful while waiting "

// block on the child let! result = childWorkflow

// done printfn "Finished parent" }

// run the whole workflow Async.RunSynchronously nestedWorkflow MailboxProcessor Basic example let printerAgent = MailboxProcessor.Start(fun inbox->

// the message processing function let rec messageLoop() = async {

// read a message let! msg = inbox.Receive()

// process a message printfn "message is: %s" msg

// loop to top return! messageLoop() }

// start the loop messageLoop() ) printerAgent.Post "hello" printerAgent.Post "hello again" printerAgent.Post "hello a third time" Demo Type providers Type providers

▪ Based on external data source ▪ Provide types, properties, and methods ▪ Generate Provided Types ▪ Instant autocomplete Demo Fable Fable

▪ F# to JavaScript compiler ▪ Generates JavaScript which you could be proud of ▪ Easy JavaScript interop (use NPM packages!) ▪ Supports most of the F# core library ▪ Online REPL Demo Scott Wlaschin

https://www.fsharpforfunandprofit.com/ QUESTIONS Competition results Mikhail Smal

https://mikhail.dev

Workshop: Web development in F# with SAFE stack

2 November 2019