val thisIsAnInt : int

Full name: index.thisIsAnInt
val thisIsAString : string

Full name: index.thisIsAString
val optionalTypeAnnotation : bool

Full name: index.optionalTypeAnnotation
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
val listOfFloats : float list

Full name: index.listOfFloats
val arrayOfStrings : string []

Full name: index.arrayOfStrings
val add : a:int -> b:int -> int

Full name: index.add
val a : int
val b : int
val sign : num:int -> string

Full name: index.sign
val num : int
val fib : n:int -> int

Full name: index.fib
val n : int
val printer : f:float -> unit

Full name: index.printer
val f : float
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val i : float
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val value : 'a
val fn : ('a -> 'b)
type User =
  {FirstName: string;
   LastName: string;
   Age: int;}

Full name: index.User
User.FirstName: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
User.LastName: string
User.Age: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val kai : User

Full name: index.kai
val cloneOfKai : User

Full name: index.cloneOfKai
val olderKai : User

Full name: index.olderKai
type Shape =
  | Rectangle of width: float * length: float
  | Circle of radius: float
  | Triangle of float * float * float

Full name: index.Shape
union case Shape.Rectangle: width: float * length: float -> Shape
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
union case Shape.Circle: radius: float -> Shape
union case Shape.Triangle: float * float * float -> Shape
val rectangle : Shape

Full name: index.rectangle
val circle : Shape

Full name: index.circle
val triangle : Shape

Full name: index.triangle
val whichShape : shape:Shape -> unit

Full name: index.whichShape
val shape : Shape
val width : float
val length : float
val radius : float
val side1 : float
val side2 : float
val side3 : float
Multiple items
module Option

from Microsoft.FSharp.Core

--------------------
type Option<'a> =
  | Some of 'a
  | None

Full name: index.Option<_>
union case Option.Some: 'a -> Option<'a>
union case Option.None: Option<'a>
val validString : string option

Full name: index.validString
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val invalidString : string option

Full name: index.invalidString
val words : string list

Full name: index.words
val foundWord : string option

Full name: index.foundWord
val tryFind : predicate:('T -> bool) -> list:'T list -> 'T option

Full name: Microsoft.FSharp.Collections.List.tryFind
val x : string
val missingWord : string option

Full name: index.missingWord
val printString : input:Option<string> -> unit

Full name: index.printString
val input : Option<string>
val s : string
type ContactUser =
  {Username: string;
   Email: string option;
   PhoneNumber: string option;}

Full name: index.ContactUser
ContactUser.Username: string
ContactUser.Email: string option
ContactUser.PhoneNumber: string option
val createUser : username:'a -> emailAddress:'b -> phoneNumber:'c -> ContactUser

Full name: index.createUser
val username : 'a
val emailAddress : 'b
val phoneNumber : 'c
type ContactInformation =
  | Email of string
  | PhoneNumber of string
  | EmailAndPhone of string * string

Full name: index.ContactInformation
union case ContactInformation.Email: string -> ContactInformation
union case ContactInformation.PhoneNumber: string -> ContactInformation
union case ContactInformation.EmailAndPhone: string * string -> ContactInformation
type SafeContactUser =
  {Username: string;
   Contact: ContactInformation;}

Full name: index.SafeContactUser
SafeContactUser.Username: string
SafeContactUser.Contact: ContactInformation
val email : ContactInformation

Full name: index.email
val phoneNumber : ContactInformation

Full name: index.phoneNumber
val emailAndPhone : ContactInformation

Full name: index.emailAndPhone
val user1 : SafeContactUser

Full name: index.user1
val user2 : SafeContactUser

Full name: index.user2
val user3 : SafeContactUser

Full name: index.user3
val user4 : SafeContactUser

Full name: index.user4
val user5 : SafeContactUser

Full name: index.user5
val parseDateTime : dateTime:string -> 'a

Full name: index.parseDateTime
val dateTime : string
val getYear : date:System.DateTime -> int

Full name: index.getYear
val date : System.DateTime
namespace System
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
System.DateTime()
   (+0 other overloads)
System.DateTime(ticks: int64) : unit
   (+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : unit
   (+0 other overloads)
property System.DateTime.Year: int
val date : System.DateTime

Full name: index.date
val currentYear : int

Full name: index.currentYear
val composedGetYear : (string -> int)

Full name: index.composedGetYear
val yearFromComposed : int

Full name: index.yearFromComposed
val add : x:int -> y:int -> int

Full name: index.add
val x : int
val y : int
val threeParams : firstName:'a -> middleName:'b -> lastName:'c -> unit

Full name: index.threeParams
val firstName : 'a
val middleName : 'b
val lastName : 'c
val curriedAdd : (int -> int)

Full name: index.curriedAdd
Multiple items
val double : (int -> int)

Full name: index.double

--------------------
type double = System.Double

Full name: Microsoft.FSharp.Core.double
val sum : list:int list -> int

Full name: index.sum
Multiple items
val list : int list

--------------------
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val reduce : reduction:('T -> 'T -> 'T) -> list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.reduce
val freeSum : (int list -> int)

Full name: index.freeSum
val batchesOf : n:int -> (seq<'a> -> seq<seq<'a>>)

Full name: index.batchesOf
module Seq

from Microsoft.FSharp.Collections
val mapi : mapping:(int -> 'T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.mapi
val i : int
val v : 'a
val groupBy : projection:('T -> 'Key) -> source:seq<'T> -> seq<'Key * seq<'T>> (requires equality)

Full name: Microsoft.FSharp.Collections.Seq.groupBy
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val doubleAndIncrement : x:int -> int

Full name: index.doubleAndIncrement
val freeDoubleAndIncrement : (int -> int)

Full name: index.freeDoubleAndIncrement
val result : string list * int

Full name: index.result
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val username : string
val replies : string list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val count : int
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T




Introduction to F# for greater type safety in .NET




What is Functional Programming

  • Focus on data flow
  • Function Composition over Inheritance
  • Expressions over Statements

Common traits of the Functional paradigm

  • Higher order functions
    • High level abstractions
    • Code reusability
  • Pure Functions
    • Easier to reason about
    • Idempotency
  • Immutability
    • Predictability
    • Thread safety
    • Less bugs
  • "If it compiles, it works"
  • "Pit of Success"

What is F#

  • Functional first, multi-paradigm language
  • Runs on .Net (and Mono, Xamarin, .Net Core)
  • First released in 2005 by Microsoft Research
  • Now belongs to The F# Software Foundation
  • Open Source

Syntax in a nutshell

  • ML syntax
  • Statically Typed with type inference
  • Whitespace significant
  • Expression-based
  • Immutable by default

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let thisIsAnInt = 1
let thisIsAString = "This is a string"
let optionalTypeAnnotation: bool = true

let listOfFloats = [ 1.0; 2.0; 3.0 ]
let arrayOfStrings =
    [| "www.zuehlke.com"
       "www.duckduckgo.com"
       "www.microsoft.com" |]

Functions

  • First class functions
  • Can be passed around like other values
  • Last expression is the return "statement"

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let add a b =
    a + b

let sign num =
    if num > 0 then "positive"
    elif num < 0 then "negative"
    else "zero"

let rec fib n =
    match n with
    | 1 -> 1
    | 2 -> 1
    | n -> fib(n-1) + fib(n-2)

Piping


 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let printer f = printfn "Number is: %f" f

[0.0..100.0]
|> List.filter (fun i -> i % 2.0 = 0.0)
|> List.map (fun i -> i ** 2.0)
|> List.iter printer
// |> List.iter (fun i -> printer i)

let (|>) value fn =
    fn value

Records

  • Simple aggregates of data
  • Can be struct or reference types
  • Has structural equality

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type User =
    { FirstName: string
      LastName: string
      Age: int }

let kai = { FirstName = "Kai"; LastName = "Ito"; Age = 88 }
let cloneOfKai = { FirstName = "Kai"; LastName = "Ito"; Age = 88 }

printfn "%b" (kai = cloneOfKai) // true

let olderKai = { kai with Age = kai.Age + 1 }

printfn "%i" (olderKai.Age) // 89
printfn "%i" (kai.Age) // 88

Discriminated Unions

  • More powerful enum
  • Data point that can have multiple different types

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type Shape =
| Rectangle of width: float * length: float
| Circle of radius: float
| Triangle of float * float * float

let rectangle: Shape = Rectangle (2.0, 5.0)
let circle: Shape = Circle 2.5
let triangle: Shape = Triangle (6.1, 2.0, 3.7)

let whichShape (shape: Shape) =
    match shape with
    | Rectangle (width, length) ->
        printfn "Rectangle with sides %f %f" width length
    | Circle radius ->
        printfn "Circle with radius %f" radius
    | Triangle (side1, side2, side3) ->
        printfn "Triangle with sides %f %f %f" side1 side2 side3

Benefits of the F# type system

  • No null
  • Make illegal states unrepresentable
  • Use types to represent the domain
  • Types can also be used to encode business logic
  • Files and code must be in dependency order

The Option type

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
type Option<'a> =
| Some of 'a
| None

let validString: string option = Some "text"
let invalidString: string option = None

let words = [ "hello"; "world"; "from"; "home"; ]
let foundWord = words |> List.tryFind (fun x -> x = "world")
let missingWord = words |> List.tryFind (fun x -> x = "outside")

printfn "The word is: %s" foundWord
// Compile Error: Type mismatch: Expecting "string" but got "string option"

let printString input =
    match input with
    | Some s -> printfn "The word is: %s" s
    | None -> printfn "Didn't find word"

printString foundWord // The word is: world
printString missingWord // Didn't find word

Making illegal state unrepresentable

  • Imagine business logic where a User either needs an email address or phone number or both
  • Required to have at least one of them
1: 
2: 
3: 
4: 
5: 
6: 
7: 
type ContactUser = { Username: string; Email: string option; PhoneNumber: string option }

let createUser username emailAddress phoneNumber =
    // Logic to assign either email address or phone or both
    // Error prone

    { Username = "kaiito"; Email = "kai.ito@zuehlke.com"; PhoneNumber = "123 456 789" }

F# Type System to the rescue

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type ContactInformation =
| Email of string
| PhoneNumber of string
| EmailAndPhone of string * string

type SafeContactUser = { Username: string; Contact: ContactInformation }

let email = Email "kai.ito@zuehlke.com"
let phoneNumber = PhoneNumber "123 456 789"
let emailAndPhone = EmailAndPhone ("kai.ito@zuehlke.com", "123 456 789")

let user1 = { Username = "kaiito1"; Contact = email }
let user2 = { Username = "kaiito2"; Contact = phoneNumber }
let user3 = { Username = "kaiito3"; Contact = emailAndPhone }

let user4 = { Username = "kaiito4"; Contact = null } // Compiler Error
let user5 = { Username = "kaiito5"; Contact = "someString" } // Compiler Error

Dependency Order

Function Composition

  • Compose multiple functions into one function
  • Code reusability without verbosity
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let parseDateTime (dateTime: string) = DateTime.Parse(dateTime)
let getYear (date: System.DateTime) = date.Year

let date = parseDateTime "26-04-2020 12:00am"
let currentYear: int = getYear date

printfn "The current year is: %i" currentYear
// The current year is: 2020

let composedGetYear: string -> int = parseDateTime >> getYear
let yearFromComposed: int = composedGetYear "26-04-2020 12:00am"

printfn "The current year from composed function is: %i" yearFromComposed
// The current year from composed function is: 2020

Function Currying and Partial Function Application

  • Creating new functions by not supplying all parameters
  • F# curries all functions by default
  • What does x: int -> y: int -> int mean

Curried function

1: 
2: 
let add x y =
    x + y
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let add x =
    fun y ->
        x + y

let threeParams firstName middleName lastName =
    printfn "Full name is: %O, %O, %O" firstName middleName lastName

let threeParams firstName =
    fun middleName ->
        fun lastName ->
            printfn "Full name is: %O, %O, %O" firstName middleName lastName

Partial Function Application

  • Using curried functions
  • Only supply some of the parameters
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let add x y =
    x + y

let curriedAdd = add 3
printfn "Result is: %i" (curriedAdd 5)
// Result is: 8

let double = (*) 2

[1..10]
|> List.map double
// [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

Point-free programming

  • What happens when you abuse currying and partial function application
  • Avoid explicitly specifying parameters
  • Use Higher-order functions everywhere
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let sum list = List.reduce (+) list
let freeSum = List.reduce (+)

let batchesOf n =
    Seq.mapi (fun i v -> i / n, v) >>
    Seq.groupBy fst >>
    Seq.map snd >>
    Seq.map (Seq.map snd)

[0..10]
|> batchesOf 2 // [[0; 1]; [2; 3]; [4; 5]; [6; 7]; [8; 9]; [10]]

let doubleAndIncrement x = x * 2 + 1
let freeDoubleAndIncrement = (*) 2 >> (+) 1

Demo

Railway Oriented Programming

  • Error handling through function composition
  • Clean control flow
  • Treat errors as first class citizens

Problems with the Imperative approach

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
public string UpdateAndSend(string input)
{
    if (string.IsNullOrWhitespace(input))
        return "empty input"; // Or should we throw an Exception?

    try {
        var updatedUser = UpdateUserInDatabase(input);
        return ConvertToJson(updatedUser);
    }
    catch (DatabaseException e) {
        logger.log(e.Message);
        return "Problem updating user in DB"; // Or should we rethrow?
    }
}

Demo

Monads

  • A monad is just a monoid in the category of endofunctors
  • Chainable wrapper around a data structure that performs an extra operation after each expression
  • Semicolon at end of statement performs extra action

Monad in Detail

  • A constructor that wraps a value: the monadic value M
  • A bind function that accepts a function as its parameter
    • Applies this function to the internally wrapped value M
    • Returns the function’s output wrapped as a monad
  • A return function that simply unwraps the monadic value

JavaScript Promise

1: 
2: 
3: 
4: 
5: 
const myPromise =
    (new Promise(() => getDataFromBackend())))
    .then(response => callThirdPartyApi(response.username))
    .then(response => updateHtml(response.data))
    .catch(error => console.log(error));

F# Computation Expression

  • NOT Monads
  • Can be used to express monads
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let result =
    async {
        let! (username: string) = getByIdAsync 1
        let! (replies: string list) = getRepliesByUsernameAsync username

        let count = replies |> List.length

        return (replies, count)
    } |> Async.RunSynchronously

Demo

Additional resources

Thank you!

  • Questions?
  • Feedback?