
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 programming language ▪ 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 (Xamarin, React Native) Tooling ▪ Visual Studio ▪ Visual Studio Code (Ionide extension) ▪ JetBrains Rider ▪ MonoDevelop ▪ vim-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) -> '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<int> [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 system type [<Measure>] g type [<Measure>] inch type Gramms = Gramms of int<g> type Inches = Inches of float<inch> 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<C> | Sunny | Wet | Windy of Direction * windspeed:float<m/s> let (|Low|Medium|High|) speed = // Active patterns if speed > 10.<m/s> then High elif speed > 5<m/s>. then Medium else Low match weather with | Cold temp when temp < 2.0<C> -> "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<Input,’Failure> Bind example bind nameNotBlank bind name50 bind emailNotBlank // (bind nameNotBlank): Result<Input,’Failure> -> Result<Input,’Failure> Bind example bind nameNotBlank >> bind name50 >> bind emailNotBlank Bind example let validateRequest = bind nameNotBlank >> bind name50 >> bind emailNotBlank // validateRequest: Result<Input,’Failure> -> Result<Input,’Failure> Bind example let (>>=) twoTrackInput switchFunction = bind switchFunction twoTrackInput let validateRequest twoTrackInput = twoTrackInput >>= nameNotBlank >>= name50 >>= emailNotBlank Further
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages75 Page
-
File Size-