F#

a brief introduction

 

by Daniel Bachler (@danyx23)

Overview

  • A tour of the language
  • Walkthrough of a practical example
  • Q&A

What I like about F#

  • Can be used for a lot of different scenarios: Console applications, cross platform native UI apps, scripts, web applications, web UIs, data science notebooks, ...
  • ADTs and type inference lead to high productivity and no slowdown of development speed over time
  • Defaults to immutability, functions + data
  • But supports mutability, object oriented features, ...
  • Great community

Values

and type inferrence

let aBoolean = true
let someInt = 5
let someFloat = 4.3
let someString = "This is a string value"
let ( someOtherInt : int ) = 42
let tuple = (4, "a string value")
let listA = [ 1; 2; 3 ]

let listB : List<int> =
    [ 1
      2
    ]
    
let listC = 1 :: [ 2; 3]

let someArray : Array<string> = 
    [| "This"; "is"; "an"; "array" |]
let secondElement = someArray.[1]

Comparing syntax

(Javascript)

function addNumbers(first, second) {
    return first + second;
}

addNumbers(3, 4);

Comparing syntax

(F#)

let      addNumbers(first, second) =
           first + second


addNumbers(3, 4)

Comparing syntax

(F#)

let addNumbers (first, second) =
    first + second


addNumbers(3, 4)

Yes, F# is white space sensitive

Comparing syntax

(F#)

let addNumbers (first : int, second : int) : int =
    first + second


addNumbers(3, 4)

Currying &

partial application

let addNumbers (first : int) (second : int) : int =
    first + second


addNumbers 2 4
// 6
let addFive =
    addNumbers 5

addFive 10
// 15

Records

type Person =
    { FirstName : string
      LastName : string
      YearOfBirth : int }

let batman = 
    { FirstName = "Bruce"
      LastName = "Wayne"
      YearOfBirth = 1939
    }

let printPerson (person : Person) =
    printfn "%s %s (Born %d)" person.FirstName 
        person.LastName person.YearOfBirth

printPerson batman
// Bruce Wayne (Born 1939)

let theDarkKnight = 
    { batman with YearOfBirth = 1986 }
             

Discriminated Unions

(Sum types)

type Direction =
    | Up
    | Right
    | Left
    | Down

let printDirection (dir : Direction) =
    match dir with
    | Up -> printfn "Up"
    | Right -> printfn "Right"
    | Left -> printfn "Left"
    | Down -> printfn "Down"

printDirection Up
             

Discriminated Unions

(Sum types)

type Status<'successType> =
    | Pending
    | Running of current : int * total : int
    | Success of 'successType
    | Error of string
let printStatus status =
    match status with
    | Pending -> printfn "Pending"
    | Running(0, n) -> printfn "Starting with first item of %d" n
    | Running(i, n) -> printfn "Processing %d of %d" i n
    | Success success -> 
        printfn "Succesfully completed, result is %O" success
    | Error msg -> printfn "Error: %s" msg
printStatus (Running(2, 4))
// Processing 2 of 4

printStatus (Success 42)
// Succesfully completed, result is 42

Optional values and errors

// Defined in the standard library
type Option<'a> =
    | None
    | Some of 'a

type Result<'success, 'error> =
    | Ok of 'success
    | Error of 'error
    
let safeDivision (x : float) (y : float) : Option<float> =
    match y with
    | 0.0 -> None
    | nonzero -> Some (x / nonzero)

let safeDivision2 (x : float) (y : float) : Result<float, string> =
    match y with
    | 0.0 -> Error "Division by zero"
    | nonzero -> Ok (x / nonzero)
    
let result = safeDivsision2 1.0 0.0
match result with
| Ok r -> printfn "Result is %f" r
| Error e -> printfn "Error: %s" e

Handling Errors

Exceptions

let divide (x : float) (y : float) : float =
    x / y

let doCalculation (x : float) (y : float) : float =
    try 
        divide x y
    with
    | :? DivideByZeroException as ex -> printfn "y was 0!"

Mutation

let mutable i = 0 
while i < 4 do
    printfn "i is now %d" i
    i <- i + 1

let processInstance = new System.Diagnostics.Process()
processInstance.StartInfo.FileName <- "echo"
processInstance.StartInfo.CreateNoWindow <- true
processInstance.Start();

Now for some less common features

Many options are annoying

let createPerson (context : HttpContext) : Option<Person> =
    let firstNameOption = context.TryGetQueryParam "firstname"
    let lastNameOption = context.TryGetQueryParam "lastname"
    let yearBornOption = context.TryGetQueryParam "yearBorn"
    
    match firstNameOption, lastNameOption, yearBornOption with
    | Some firstName, Some lastName, Some yearBorn -> 
        Some { FirstName = firstName
               LastName = lastName
               YearOfBirth = yearBorn }
    | _ -> None

Wouldn't it be nice if

A library could provide blocks that define new semantics for sequencing expressions?

 

E.g. if any of these expressions gives None, return None

Computation Expressions

let createPerson (context : HttpContext) : Option<Person> =
    option {
        let! (firstName : string) = context.TryGetQueryParam "firstname"
        let! (lastName : string) = context.TryGetQueryParam "lastname"
        let! (yearBorn : string) = context.TryGetQueryParam "yearBorn"
    
        return { FirstName = firstName
                 LastName = lastName
                 YearOfBirth = yearBorn }
    }

Computation Expressions

let retrieveFromRemoteStore key : Async<string> =
    // ommited
    
let createPerson (context : HttpContext) : Async<Person> =
    async {
        let! firstName = retrieveFromRemoteStore "firstname"
        let! lastName = retrieveFromRemoteStore "lastname"
        let! yearBorn = retrieveFromRemoteStore "yearBorn"
    
        return { FirstName = firstName
                 LastName = lastName
                 YearOfBirth = yearBorn }
    }

Computation Expressions

let aSequence : seq<int> = 
    seq {
    	1 .. 5
    }

let bSequence : seq<int> =
    seq {
    	yield 0
        yield! aSequence
        yield 23
    }
    

Units of Measure

[<Measure>] type m
[<Measure>] type sec
[<Measure>] type kg

let distance = 
    1.0<m> // float<m>
    
let time =
    2.0<sec> // float<sec>
    
let speed =
    distance/time // float<m/sec>
    
let acceleration =
    speed/time // float<m/sec^2>
    
let mass =
    5.0<kg> // float<kg> 
    
let force =
    mass * speed/time // float<kg*m/sec^2>
             

Type Providers

// berlin-weather.csv
// Date, Minimum Temperature, Maximum Temperature
// 2020-01-01, -3.4, 2.5
// ...

type WeatherHistory = CsvProvider<"berlin-weather.csv">
// type WeatherHistoryRow =
//   { Date : System.DateTime
//     ``Minimum Temperature``: float
//     ``Maximum Temperature``: float }

let berlinHistory = WeatherHistory.Load("berlin-weather.csv")

for row in berlinHistory.Rows do
    printfn "%O: minimum was %f" row.Date row.``Minimum Temperature`` 

the SAFE Stack

F# on the back end and the front end

A template that sets up:

  • An HTTP application web server using ASP.NET Core
  • A Fable (F# → Javascript) example SPA using React for rendering and Elmish for state management
  • Bulma as the default styling framework
  • A shared projects with types and functions that can be used on the server and the client
  • A Webpack setup for hot reloading during development and bundling for production

The server

/src/Server

"/api/getTodos" => Unit -> List<Todo>

"/api/addTodo" => Todo -> Todo


can also serve files

/index.html -> ./assets/index.html

The SPA

/src/Client

type Model = 
    { Todos: Todo list
      Input: string}
type Msg =
    | GetTodos of Todo list
    | SetInput of string
    | AddTodo
    | AddedTodo of Todo
let init(): Model * Cmd<Msg> = // ...
let update
    (msg : Msg) 
    (model: Model)
    : Model * Cmd<Msg> = // ...
let view 
    (model : Model) 
    (dispatch : Msg -> unit)
    : ReactElement = //..

Shared code

/src/shared

type Todo =
    { Id : Guid
      Description : string }

type ITodosApi =
    { getTodos : unit -> Async<Todo list>
      addTodo : Todo -> Async<Todo> }

Demo time

3 years of using F#

The positives

  • Same language on Frontend, Backend and even in make files is great
  • Algebraic Data Types are great
  • Big ecosystems (.NET and Javascript) are very useful
  • Asp.Net core is fast and works nicely with linux docker containers

3 years of using F#

The negatives

  • Documentation of F#/Fable libraries sometimes lacking
  • Community is a lot smaller than some other languages
  • No great meta-programming solutions

 

Where to find more

Currying &

partial application

// defined in the standard library
module List =

  let rec map (mapFn : 'a -> 'b) (list : List<'a>) : List<'b> =
      match list with
      | [] -> []
      | [ item ] -> [ mapFn item ]
      | item :: rest -> mapFn item :: map mapFn rest
    
List.map (fun x -> sprintf "%d" x) [ 1; 2; 3]

[ 1; 2; 3]
|> List.map (fun x -> sprintf "%d" x) 

Computation Expressions

let optionBind (f : 'a -> Option<'b>) (value : Option<'a>) =
    match value with
    | None -> None
    | Some v -> f v

type OptionBuilder() =
    member this.Return(x) = 
        Some x
    member this.Bind(m, f) =
        Option.bind f m

let option = OptionBuilder()

Computation Expressions

let createPerson (context : HttpContext) : Option<Person> =
    option {
        let! firstName = context.TryGetQueryParam "firstname"
        let! lastName = context.TryGetQueryParam "lastname"
        let! yearBorn = context.TryGetQueryParam "yearBorn"
    
        return { FirstName = firstName
                 LastName = lastName
                 YearOfBirth = yearBorn }
    }

let createPerson (context : HttpContext) : Option<Person> =
    context.TryGetQueryParam "firstname"
    |> optionBind 
        (fun firstName ->
            context.TryGetQueryParam "lastname"
            |> optionBind 
                (fun lastName ->
                    context.TryGetQueryParam "yearOfBirth"
                    |> optionBind 
                        (fun yearBorn ->
                            { FirstName = firstName
                              LastName = lastName
                              YearOfBirth = YearOfBirth
                            })
                    )
    )

Why I like F#

By Daniel Bachler