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
Why I like F#
- 984