F#

a brief introduction

 

by Daniel Bachler (@danyx23)

What is F#?

  • A multi-paradigm language
    (functional, imperative, object oriented)
  • "functional first"

 

A brief history

  • Started out in the early 2000s as an OCaml for .NET
  • With the .NET Core Framework (~2016) cross platform use becomes much move viable
  • From 2015 Fable compiler (F# compiler backend that generates Javascript)

Values

and type inferrence

module Demo

let aBoolean = true
let someInt = 5
let someFloat = 4.3
let someString = "This is a string value"
let ( someOtherInt : int ) = 42

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

Currying &

partial application

module Demo

let addNumbers first second =
    first + second

let addFive =
    addNumbers 5

addFive 10
// 15

Calling framework functions and using classes

module Demo

let result = System.Math.Sin 1.0

open System.Math

let result2 = Sin 1.0

let randomGenerator = System.Random()
let randomValue = randomGenerator.Next()

Tuples

module Demo

let tupleValue = (5, "a string")

let printTuple (tuplevalue : int * string) =
    let (firstPart, secondPart) = tupleValue
    printfn "The number is %d, the string is '%s'" firstPart secondPart

printTuple tuple

Tuples

module Demo

let tupleValue = (5, "a string")

let printTuple (firstPart : int, secondPart : string) =
    printfn "The number is %d, the string is '%s'" firstPart secondPart

printTuple tuple

Records

module Demo

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)
             

Records

module Demo

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

let theDarkKnight = 
    { batman with YearOfBirth = 1986 }

printPerson batman
// Bruce Wayne (Born 1939)
printPerson theDarkKnight
// Bruce Wayne (Born 1986)
             

Discriminated Unions

(Sum types)

module Demo

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)

module Demo

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 (Success 42)
// Succesfully completed, result is 42
printStatus (Error "unknown problem")
// Error: unknown problem
             
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 (Success 42)
// Succesfully completed, result is 42
printStatus (Error "unknown problem")
// Error: unknown problem
             
printStatus (Success 42)
// Succesfully completed, result is 42
printStatus (Error "unknown problem")
// Error: unknown problem
             

Handling optional values

Option

module Demo

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

let doSomething (optionalInteger : Option<int>) =
    match optionalInteger with
    | Some value -> printfn "Got %d" value
    | None -> printfn "Didn't have a value!"

Handling optional values

null

module Demo

let doSomething (person: Person) =
    match person with    
    | null -> printfn "Didn't have a value!"
    | personValue -> printfn "Got %O" personValue

Handling Errors

Result

module Demo

type Result<'success, 'error> =
| Ok of 'success
| Error of 'error

let doSomething (intResult : Result<int, string>) =
    match intResult with
    | Ok value -> printfn "Got %d" value
    | Error err -> printfn "Got an error: %s" err

Handling Errors

Exceptions

module Demo

try 
    someExceptionThrowingFunction()
with
| :? IOException as ex -> printfn "IOException was thrown: %O!" ex
| _ -> printfn "Some other exception was thrown!"

Mutation

module Demo

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

             

Many options are annoying

module Demo

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

Computation Expressions

module Demo

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

Computation Expressions

module Demo

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 }
    }

Units of Measure

module Demo

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

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

Type Providers

module Demo

let msft = Stocks.Load("http://www.google.com/finance/historical?q=MSFT&output=csv")

// Look at the most recent row. Note the 'Date' property
// is of type 'DateTime' and 'Open' has a type 'decimal'
let firstRow = msft.Rows |> Seq.head
let lastDate = firstRow.Date
let lastOpen = firstRow.Open

// Print the prices in the HLOC format
for row in msft.Rows do
  printfn "HLOC: (%A, %A, %A, %A)" row.High row.Low row.Open row.Close

A year 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

A year of using F#

The negatives

  • Documentation of F#/Fable libraries sometimes lacking
  • Some pain points around library packaging if code should be used on Backend and Frontend

 

Where to find more

Intro to F#

By Daniel Bachler

Loading comments...

More from Daniel Bachler