F# introduction for my coworkers
A quick, dirty, and mostly wrong introduction to F#
Table of Contents
- Resources
- Tooling
- Functional programming
- F#
- .NET
- Basics
- A simple Number Guessing Game
- Walk-through
module
: Module and namespace declarationopen System
: Importing symbols from modules or namespacesmodule Option
: Extending modules and the Option type[<Literal>]
: Attributes, constants and type inferencetryWithin
: Functions and constraintstryAnswerBounds
: Partial Function ApplicationtryReadInt
: Composing functionsguessNumber
: Sequences and more on composingRandom.Next
: Using other .NET classes and methods- The main loop
- Conclusion
- Walk-through
- Literals
- Functions
unit
(void)- Lists and sequences
- Products
- Discriminated Unions (CoProducts / Sums)
- Iteration
- Pattern matching
- Mapping
This is a quick introduction to F# some essential features we use in our project at work. I’ve written this for my coworkers with little to no experience in F# or functional programming to get them up to speed on the basics needed to read, understand and contribute to our projects.
Given these constraints, this introduction is not an in-depth guide, neither is it complete, correct, nor deep in theory. I’ll be focusing on the most important features needed to understand our projects and nothing more.
Send corrections and comments to my email.
Resources
- FSharp.org
- F# foundation homepage
- F# For Fun And Profit
- Highly recommended. Teaching F# and functional programming in a very approachable way.
- Microsoft F# Guide
- Introduction to F#
- Language Reference
- Language reference on Microsoft. Not sure if it’s updates, bit this is the reference I’ve used myself.
- Visual F# documentation
- ..
Tooling
While the tooling for F# is a lot better than many languages, it’s still not nearly as complete as you’d expect from C# or Java.
Visual Studio Code
Use Ionide. This is probably the easiest to use, although I’ve not used it myself.
Visual Studio
F# tooling has shipped with Visual Studio for some time, but it might have to be enabled in the installer depending on the workload you chose when installing. You can download the latest version from within VS. See documentation on the fsharp.org page for more information.
For any non-trivial projects that also has a mix of C# projects, this is probably the way to go. You might be able to use Visual Studio Code for parts of the development.
REPL
F# works well with a Read Eval Print Loop (REPL), and this is available through
editors or on the command-line by calling fsi.exe
(or fsharpi
depending on
your OS). This is useful for looking at types and experiment with the language.
By just writing the name of a function, you’ll see the type:
$ fsharpi F# Interactive for F# 4.0 (Open Source Edition) Freely distributed under the Apache 2.0 Open Source License For help type #help;; > (|>);; val it : ('a -> ('a -> 'b) -> 'b) = <fun:it@1> > #quit;; - Exit...
Here we see that the function |>
has the type 'a -> ('a -> 'b) -> 'b
. Don’t
worry, we’ll get to all this. Using the REPL to look at type signatures and
experiment.
fsharpi
can also run scripts.
printfn "Hello, World!"
$ fsharpi hello.fsx Hello, World!
Although using the REPL is great, using it from within an editor with nice integration for executing parts of scripts is more convenient and what I do in my daily work.
emacs
Use fsharp-mode. The Spacemacs distribution with the FSharp layer works well. This is my setup of choice for much of my experimentation.
This introduction is written in emacs, using the Spacemacs distribution with evil-mode, fsharp-mode, org-mode and org-babel.
vim
I’ve not yet tried using F# from vim.
Functional programming
Functional programming is very different from OOP, so don’t be alarmed if lots of the material here seems foreign and is difficult to grasp. This introduction is not indented to teach functional programming, but will hopefully be a kick-start to reading code and give some terminology to help self study by searching for more in-depth information elsewhere. It’s perfectly possible to use F# without learning a lot of FP theory.
“F# for fun and profit” is probably the best resources for starting. This, along with the language reference, was my resources when learning F#. I recommend going to the Site Contents and start from the top. If you’re motivated to spend some real time learning F#, you might as well just start with that site instead of this tutorial.
Haskell Programming From First Principles is a nice introduction text to Haskell, and explains a much of the functional programming parts too. Even though it’s for another language, a lot of what you learn will be useful in your daily F# coding too. The book is written for people without experience with programming at all, but don’t assume this will make the book an easy read. It will require a lot of mental effort to work through, but it’s well worth it.
One of the biggest difference when coming from OOP is how to organize code and data. In OOP, we organize by hiding state within objects, and design the objects for inheritance or composition. Calling methods on these objects might, and often will, modify its internal state. Note that “composition” in OOP terms is very different from “composition” in FP terms.
In many FP languages, we don’t mutate state (although F# allows you to), and data and behavior is separated. F# allows, and somewhat encourage, grouping behavior with data, but this will reduce our possibilities for composing behavior. Immutability is important in order to reason about code, for managing concurrency and ease debugging.
F#
F# is a multi-paradigm language, supporting both Functional Programming (FP) and Object-Oriented Programming (OOP). While OOP is fully supported, the language encourage FP, and is said to be a “functional first” programming language. We’ll focus on the functional aspects as this is what we have in our projects and the thing that is most foreign.
.NET
F# is able to leverage the existing .NET libraries and tools, which means you don’t have to learn an entirely new ecosystem, standard libraries, or supporting libraries. Everything that works in C# will work just as well in F# in the same way. Many even strongly believe F# does OOP better than C#.
If you try to expose F# code to other .NET languages, you’ll have to take more care as F# does many tricks to model it’s features to the CLR, which haven’t been created with FP in mind. We won’t go into details here, but the short answer is to use namespaces and classes (just like in C#) when creating an API for other .NET languages. If you try to look at the generated code or call an F# library from C#, you’ll notice that it uses a lot of static classes, and in general doesn’t look anything like ordinary C# code.
.NET tooling also assumes you’re using C# (or VB.NET for the tools that have this support). This means profilers, disassemblers and more won’t show you F# code, but rather heavily obfuscated C# code. I’ve still used such tools successfully, but expect to see a lot of the implementation details of how F# maps to the CLR.
Basics
I’m only showing the lightweight syntax of F#, but don’t be alarmed if you see code online looking a lot more verbose. F# also supports verbose syntax which allows you to be more lax with indentation.
Whitespace sensitive
F# is whitespace sensitive much like Python or Haskell. Indentation indicates a scope, and a line break is the end of a statement.
Bindings is available to everything in the scope that follows and that shadowing is allowed.
I recommend turning on indentation highlighting in your editor to make the scope more visually distinct. I also like using more blank lines in such languages to easily visually distinct newlines within functions from newlines between functions.
// we define p as 1 let p = 1 // then we create a submodule module SomeModule = // we redefine p, but we use the old p in its definition let p = p + 1 // 2 // here we define a function let someFunction () = // that defines a new p using the previous p in its definition let p = p + 1 // 3 // and we finally return this last p p + 1 // 4
sprintf "Top: %d, SomeModule: %d, someFunction: %d" p SomeModule.p (SomeModule.someFunction ())
Top: 1, SomeModule: 2, someFunction: 4
PS: I don’t encourage using lots of shadowing.
Fewer parenthesis
FP is known for having a lightweight syntax, and as functions is the most elementary building block, they are made especially lightweight.
Passing function arguments is just separated with namespaces. Given a function
f : 'a -> 'b -> 'c
, you should call it as f 1 2
rather than f(1, 2)
. The
latter means a different thing in F#, and will be actually send a tuple much
like f(Tuple.Create(1,2))
, which is not what we meant.
Valid characters in symbols
'
is a perfectly valid symbol, and is often used when there are small
differences in a function – you’ll be creating a lots of functions, and naming
everyone would be very difficult.
It’s also possible to create names that contain other special characters:
let f' x = x let ``[Function] *with /a -- _special_ "name"`` x = x
let f' x = x let ``[Function] *with /a -- _special_ "name"`` x = x;; val f': x: 'a -> 'a val ``[Function] *with /a -- _special_ "name"`` : x: 'a -> 'a
You might be used to writing tests with names like
WhenCallingSomeFunction_WithSomeSpecialState_ThrowsException
. In F#, this
would be
let ``When calling SomeFunction with some special state throws exception`` () = ()
let ``When calling SomeFunction with some special state throws exception`` () = ();; val ``When calling SomeFunction with some special state throws exception`` : unit -> unit
This will make it a lot simpler to read test results.
Reading types
Creating new types easy and safe, so you’ll probably create a lot of types.
Types in F# will have structural equality by default, so you don’t have to
override Equals
or GetHashCode
. Together with simple syntax, this encourage
programming with types. Reading the types is a bit different than C# though.
The basic syntax is theSymbol : firstParam -> secondParam -> result
. I’ll
never write names like this when explaining something, so let’s talk about
f : a -> b -> c
instead. The arrow in the type signature is right associative,
so this should be read as f : a -> (b -> c)
, which looks very wrong at first
sight. The reason for this is that every function in F# only takes a single
argument and returns a single value! A function taking two arguments is actually
a function taking one argument, returning a new function taking another
argument. The following is equivalent:
let f a b = a + b let g a = fun b -> a + b let h = fun a b -> a + b
let f a b = a + b let g a = fun b -> a + b let h = fun a b -> a + b;;
As you’ll see from the types, this is exactly the same. This becomes important
later when we talk more about functions. Notice that the parameters are no
longer just a
, but they have a type attached after :
as with the function.
Types can also be written inline as follows.
let f a b = a + b let g (a:int) (b:int) = a + b let h : int -> int -> int = fun a b -> a + b let i (a:int) (b:int) : int = a + b let j a b = (a + b) : int
let f a b = a + b let g (a:int) (b:int) = a + b let h : int -> int -> int = fun a b -> a + b let i (a:int) (b:int) : int = a + b let j a b = (a + b) : int;; val i: a: int -> b: int -> int
Notice that there are many ways we can attach types to the signatures, but the compiler is quite smart in reasoning about the types.
Generic parameters will be prefixed with '
:
let f x = x
let f x = x;;
Whenever you see _
, it means “I don’t care” or “match everything”. Look at the
following definitions that will extract the first or second element of a tuple:
let fst (a, _) = a let snd (_, b) = b
let fst (a, _) = a let snd (_, b) = b;; val fst: a: 'a * 'b -> 'a val snd: 'a * b: 'b -> 'b
It’s not possible to look at _
or otherwise use the value, and many values can
be thrown away in the same scope – this doesn’t bind something to the _
symbol. It matches anything, and throws away the result. The alert reader might
notice that I write f : a -> a
rather than f : 'a -> 'a
. This is because I’m
lazy and I think it’s pretty clear from the context that I don’t mean a data
type named a
. When I write the former, I mean the latter.
Order of compilation
Another different thing about F# is that every symbol must be defined before use. This is like C, except that you don’t have forward declaration at your disposal. For this reason, files have to be ordered, and the symbols within the files has to be ordered. When I first started, I thought it would be really difficult to structure projects this way, but it’s actually both quite natural and haven’t resulted in any difficulties for me. But it does mean that you need to look in a project file in order to see the correct order of the files to compile. In return you know that the most general stuff is at the top in the first file, and your main function will be at the bottom of the last file.
There is an escape-hatch, and
, to compile multiple symbols as a unit and thus
enabling recursion between them.
Expressions
Several things that are statements in C# is expressions in F#. In C# you might write something like the following
int res; if (true /*some condition*/) { res = 1; } else { res = 2; } // Do something with res
In F#, if
is an expression, and you would rather write
let res = if true then 1 else 2
1
The last expression will be the result. This is true for all expressions.
No nulls! (.. or rather fewer nulls)
F#, as many other functional languages, explicitly handles absence of values
with a custom type rather than with an (for common architectures) invalid memory
location that will trigger an error in the OS. In F#, this type is called
Option
. It looks and behaves much like .NETs Nullable
.
As F# has good .NET integration, we can still get null values from other
libraries, and it’s also possible to construct them ourselves if we want. But in
general, nulls doesn’t really pose a problem as in C#. Using Option
to model
the possible absence of values is highly encouraged as it forces us to
explicitly handle the cases when a value is missing. It’s fully possible to
create F# programs that doesn’t use nulls or explodes at runtime.
Consider the following F# snippet
// Person is a record, and can never be null type Person = { name : string } // As Person cannot be null, getName cannot trigger an exception let getName (p : Person) = p.name // We need to explicitly state that something might be missing // and handle the case where it's missing let getNameOrDefault (maybePerson : Option<Person>) = match maybePerson with | Some person -> person.name | None -> "John Doe"
type Person = { name : string } let getName (p : Person) = p.name let getNameOrDefault (maybePerson : Option<Person>) = match maybePerson with | Some person -> person.name | None -> "John Doe";; type Person = { name: string } val getName: p: Person -> string val getNameOrDefault: maybePerson: Option<Person> -> string
Nulls are popularly known as the billion dollar mistake, and I have no doubt this is a very kind underestimate. It might seem tedious to always be checking for nulls, but in practice you code in a different style such that this is just a big safety net that saves a lot of time even in the short run.
A simple Number Guessing Game
printfn "Hello, World"
printfn "Hello, World";; Hello, World
Well, that was pretty uninteresting.. Let’s showcase a simple number guessing game - a Hello World for the working programmer.
module NumberGuessGame open System module Option = let flatten = function | Some v -> v | None -> None [<Literal>] let MinAnswer = 1 [<Literal>] let MaxAnswer = 10 [<Literal>] let MaxTries = 3 let tryWithin a b v = if v < a || v > b then None else Some v let tryAnswerBounds = tryWithin MinAnswer MaxAnswer let tryReadInt = Console.ReadLine >> Int32.TryParse >> function | true, v -> Some v | false, _ -> None let guessNumber () = printf "Guess a number: " Seq.initInfinite ( ignore >> tryReadInt >> Option.map tryAnswerBounds >> Option.flatten ) |> Seq.pick id let answer = Random().Next(MinAnswer, MaxAnswer) Seq.init MaxTries (ignore >> guessNumber) |> Seq.exists ((=) answer) |> function | true -> "Correct" | false -> sprintf "The answer was %d" answer |> printfn "Result: %s"
Even if this is a very small program, there’s enough features here that it is impossible to read without going into more details of several features. We’ll go through the application line by line with a short explanation of what’s going on. Just skip the parts you don’t understand, and we’ll go into more detail in later sections. After you have read the relevant sections, you can come back to this example, and it will hopefully make more sense.
Walk-through
module
: Module and namespace declaration
module NumberGuessGame
F# supports modules
and namespaces
. Modules are a way to structure code, and
will usually contain data types, constants and functions that operate on these.
Namespaces is the same as in C#. It’s also possible to start with a namespace
declaration, but we won’t go into the difference now.
open System
: Importing symbols from modules or namespaces
open System
This is the same as a using
import declaration in C#. It will fill the current
scope and child scopes with symbols from System
.
module Option
: Extending modules and the Option type
module Option = let flatten = function | Some v -> v | None -> None
module M =
will declare a new module. If the module already exists, as as the
case with Option
, it will add additional symbols to that module. The Option
type is a type-safe way of encoding the concept of “value absent” rather than
using null
. This forces us to explicitly handle the absent case rather than
trying to access a missing value by mistake and failing with the dreaded
NullReferenceException
.
let
will bind a value or function to a name. Here we create a function that
will convert an Option<Option<'a>>
to Option<'a>
. 'a
means a generic
argument, much like we use T
in C#. It’s easier to see if we use a more
verbose syntax.
let flatten (x : Option<Option<'a>>) : Option<'a> = if Option.isSome x then Option.get x else None
function
is a shorthand for fun x -> match x with
, and match
will match
the structure of the Option
as well as bind the inner element. Notice that the
last value will be returned from an expression. This is true for all
expressions, including if
and functions.
[<Literal>]
: Attributes, constants and type inference
[<Literal>] let MinAnswer = 1
[<SomeAttribute>]
is the F# syntax for attaching attributes to types and
values. The LiteralAttribute
is needed to construct a compile time constant.
We don’t need to use Literal
in our application, so it’s only here to showcase
attributes. F# is good at inferring types, but when we need to, or it adds to
readability, we can add types in several ways. The following are equivalent.
let a = 1 let b = 1 : int let c : int = 1 let d : int = 1 : int
let a = 1 let b = 1 : int let c : int = 1 let d : int = 1 : int;;
tryWithin
: Functions and constraints
tryWithin
will check if a value is within a range, and return the value if it
is or no value if it’s not. It has the type 'a -> 'a -> 'a -> 'a option when
'a : comparison
, which requires some explanation. The arrow (->
) indicates
that it is a function, and the when
is a constraint on the value much like
when
in C#. 'a -> 'a -> 'a option
means that it takes two arguments of type
'a
and returns an Option<'a>
as a result. The 'a option
rather than
Option<'a>
syntax is a shorthand for a couple of built-in types, but it means
the same.
The reason why we use Some v
rather than just returning true
or false
will
become apparent later when we use the function.
let tryWithin a b v = if v < a || v > b then None else Some v
let tryWithin a b v = if v < a || v > b then None else Some v;; val tryWithin: a: 'a -> b: 'a -> v: 'a -> 'a option when 'a: comparison
tryAnswerBounds
: Partial Function Application
tryAnswerBounds
is using a feature called Partial Function Application.
tryWithin
takes three parameters, but we’re only calling it with two
arguments, which means there is one outstanding argument before it can compute a
result. tryAnswerBounds
will become a new function that awaits this last
argument before calling tryWithin
. As we have bound it with integers, it will
be a function int -> int option
. This is an important feature of FP that
allows us to create new functions that might have some context (like static data
here) already bound.
let tryAnswerBounds = tryWithin MinAnswer MaxAnswer
The function could have been written in a more explicit way
let tryAnswerBounds v = tryWithin MinAnswer MaxAnswer v
tryReadInt
: Composing functions
In tryReadInt
we’re getting to some of the most important parts of FP:
Creating new larger functions by composing several smaller functions.
Console.ReadLine
and Int32.TryParse
are the regular static .NET methods, and
you call them in the same way as you would in C#. TryParse
doesn’t take an out
parameter as that wouldn’t compose – you would have needed to create a mutable
variable first and stuff that into the function and later check it. Instead
out
parameters is returned from the method.
>>
is a regular binary infix function (like +
) that takes two functions as
arguments. Given these two functions, it will create a larger function that
first calls the left function, then passing its result to the next function.
These can be chained as we do here. It’s important that the result of the first
function and the argument of the second function is the same. Console.ReadLine
doesn’t take any arguments, which is explicit in F# by having a single parameter
of type unit
, and it returns a string
. Int32.TryParse
takes a string
and
returns a tuple bool * int
. Console.ReadLine >> Int32.TryParse
will create a
new function unit -> (bool * int)
(unit
is the type for the value ()
).
let tryReadInt = Console.ReadLine >> Int32.TryParse >> function | true, v -> Some v | false, _ -> None
We could have created another helper function to avoid the function
match:
let tryParseInt32 = Int32.TryParse >> function | true, v -> Some v | false, _ -> None let tryReadint = Console.ReadLine >> tryParseInt32
How the composition works might become clearer if we write out the functions:
let tryReadInt = fun () -> Console.ReadLine () >> fun line -> Int32.TryParse line >> fun (success, value) -> if success then Some value else None
If we were to write this in a more imperative
let tryReadInt () = let line = Console.ReadLine () let (success, value) = Int32.TryParse line if success then Some value else None
If we compare this imperative function to the the same declarative function
Console.ReadLine >> tryParseInt32
it should hopefully show that writing
functions in a more declarative way can lead to shorter, more readable,
more maintainable and more reusable code.
guessNumber
: Sequences and more on composing
guessNumber
will print Guess a number
, and then read from the console until
the user supplies an integer within our bounds.
Seq.initInfinite
will create an infinite stream of values. It takes a
generator function as argument, passing in the iteration number.
The following will create a stream of squares starting from 0:
Seq.initInfinite (fun i -> i*i) |> Seq.take 5
seq [0; 1; 4; 9; ...]
We want to create a stream of answers from the user, so we don’t care about the
integer passed into our function, for such purpose, we can use the ignore : 'a
-> ()
function. It will just throw away the value and return ()
instead.
Remember that tryReadInt
takes ()
as an argument, so we can compose these.
Option.map : 'a option -> 'b option
allows us to operate on the element within
if it exists, potentially changing the type (as we do here). If it doesn’t
exist, it’s simply a no-op and will stay a Nothing
. tryReadInt
returns a
Some value
if it’s able to parse it as a integer, and tryAnswerBounds
will
return Some value
if it’s within our bounds. By returning the value rather
than just true
/ false
, we’re able to compose these functions. Lastly, we
either have a Nothing
, Some Nothing
or Some (Some value)
, which we want to
return as Nothing
, Nothing
or Some value
respectively.
Remember that we are creating an infinite stream of answers from the user. We
need to abort when the user types a valid integer within our bounds. For this we
can use Seq.pick : 'a option -> 'a
. It will filter away all the None
values,
and when it gets a Some value
, it will extract the value.
let guessNumber () = printf "Guess a number: " Seq.initInfinite ( ignore >> tryReadInt >> Option.map tryAnswerBounds >> Option.flatten ) |> Seq.pick id
Let’s write this in a more imperative way:
let guessNumber () = printf "Guess a number: " Seq.initInfinite ( fun () -> let value = tryReadInt () if Option.isSome value then let within = tryAnswerBonuds (Option.get value) if Option.isSome within then Option.get within else None else None ) |> Seq.filter (fun x -> Option.isSome x) |> Seq.map (fun x -> Option.get x) |> Seq.head
Notice that we don’t check for null
or None
anywhere in the function
approach. We simply operate on the value if it exists, and do nothing if it’s
missing.
Random.Next
: Using other .NET classes and methods
We are using the standard random generator from .NET. We don’t have to specify
new
when instantiating a class in F#.
let answer = Random().Next(MinAnswer, MaxAnswer)
The reason we don’t specify new
is because Random
is actually a constructor
for the type. We could write it in another way:
let answer = (Random ()).Next (MinAnswer, MaxAnswer)
The reason why this is “more correct” is that Random
is actually a function
that takes the seed as an argument and returns a new Random
object – it’s the
constructor function.
System.Random
System.Random;; val it: arg00: int -> System.Random
let next : (int * int) -> int = (System.Random ()).Next
let next : (int * int) -> int = (System.Random ()).Next;; val next: (int * int -> int)
I specify the type to pick the correct overload of the Next
method. Here you
can see that Next
actually takes a tuple of min/max and returns the value in
that range.
The main loop
Lastly, we have our main loop. This will let the user try MaxTries
number of
integers, and print “Result: Correct” if we guessed it, or “Result: The answer
was <the answer>” if we failed to guess it.
Seq.init
is much like Seq.initInfinite
, but takes the number of elements it
should generate.
|>
is one of the composition functions. It is similar to >>
, but instead of
a function as the left argument, it takes a value.
We have applied all arguments to Seq.init
, so the function will actually be
called and resolve to a final value. We’ll have a sequence of MaxTries
values
where each value is within MinAnswer
and MaxAnswer
given by the user. Seq
is the same as IEnumerable
, so this is a lazily generated sequence, and the
user will only be asked to enter a new number when we request one. Seq.exists
is the same as IEnumerable.Any
, but the argument we passed in is a bit
special.
=
is also a function in F# much like any other function
(=)
(=);; val it: ('a -> 'a -> bool) when 'a: equality
Infix functions are allowed to be called as prefix functions by wrapping it in
()
, and they are allowed to be partially applied. Our (=) answer
is thus
partial function application where we bind the first parameter, and is the same
as fun x -> answer = x
.
Seq.init MaxTries (ignore >> guessNumber) |> Seq.exists ((=) answer) |> function | true -> "Correct" | false -> sprintf "The answer was %d" answer |> printfn "Result: %s"
Conclusion
I would be surprised if there are not a lot of questions after reading this walk-through. Download the program, experiment with it using an editor with a REPL, and continue reading this guide for a more thorough explanation of some of the features. Once you’ve read the rest of this guide, you should be able to read and write a simple program like this.
Literals
In order to be a CLR compile time constant, we have to annotate our binding.
let notReallyALiteral = 1 [<Literal>] let CompileTimeConstant = 1
1
Functions
Basics
A lot of the power of having functions as first-class citizens is the ability to create larger functions by composing several smaller functions together. Remember that passing a single argument to a function taking more than one parameter will create a new function.
Before we get into composition, it’s useful to look into the syntax and basic features.
Infix functions
Any function consisting only of symbols is an infix function. It’s possible to call it prefix
let (+*) a b = (a + b) * 3 let a = 1 +* 2 let b = (+*) 1 2
let (+*) a b = (a + b) * 3 let a = 1 +* 2 let b = (+*) 1 2;; val ( +* ) : a: int -> b: int -> int
Use the REPL to look at how common functions are defined.
Generic functions
Functions in F# is generic by default. The following function will swap the two elements in a tuple:
let swap (a, b) = (b, a)
let swap (a, b) = (b, a);; val swap: a: 'a * b: 'b -> 'b * 'a
Notice that the types have the types 'a
, which indicates any type. The same in
C# would look like this
Tuple<B, A> Swap<A, B>(Tuple<A, B> t) { return Tuple.Create(t.Item2, t.Item1); }
Type inference will usually understand what types should be. If a function is very generic, there’s not a lot the function can do as you have no idea what kind of operation the type could support. These kind of functions will usually compose or help with composing functions. A couple of examples:
let id x = x let flip f a b = f b a let const' a _ = a let fst (a, _) = a let snd (_, b) = b let (>>) f g x = g (f x) let (|>) a f = f a
let id x = x let flip f a b = f b a let const' a _ = a let fst (a, _) = a let snd (_, b) = b let (>>) f g x = g (f x) let (|>) a f = f a;; val id: x: 'a -> 'a val flip: f: ('a -> 'b -> 'c) -> a: 'b -> b: 'a -> 'c val const': a: 'a -> 'b -> 'a val fst: a: 'a * 'b -> 'a val snd: 'a * b: 'b -> 'b val (>>) : f: ('a -> 'b) -> g: ('b -> 'c) -> x: 'a -> 'c val (|>) : a: 'a -> f: ('a -> 'b) -> 'b
Function parameters
A function in F# only takes a single argument, but can return another function. This means the following is equivalent:
let f a b c = a + b + c let f' = fun a -> fun b -> fun c -> a + b + c
let f a b c = a + b + c let f' = fun a -> fun b -> fun c -> a + b + c;; val f': a: int -> b: int -> c: int -> int
This is very important, because it lets us bind just a single value:
let f a b c = a + b + c let g = f 10
let f a b c = a + b + c let g = f 10;;
g
here is now defined as let g b c -> 10 + b + c
. This is called partial
function application.
Composition
(>>)
(>>);; val it: (('a -> 'b) -> ('b -> 'c) -> 'a -> 'c)
This is a very generic function as you can see by the 'a
parameters. A type
name which starts with '
is a generic parameter, meaning it can take any type
as an argument. These types can be more than just a single type. For instance,
an 'a
can be (x -> y) -> Option<z>
or something even more complex. Try the
id
function with different kinds of arguments to see how it works.
('a -> 'b)
can be read as “A function taking anything as an argument,
returning anything else”. What’s important is that it might have a different
type as input and output. 'b
could also be the same as 'a
if you really
like. When mapping values in a list, you might return a value of the same type,
and thus have 'b
= 'a
:
List.map ((+) 1) [1; 2]
2 | 3 |
In order to compose functions, the output of the first has to match the input of
the second. Remember that ->
is right associative, so the function signature
can be read as (a -> b) -> (b -> c) -> (a -> c)
. This can be read as “Given a
function from a to b and a function from b to c, I’ll construct a function from
a to c”. >>
is the flipped version of \(\circ\). So f >> g
is the same as \(g
\circ f\). If you like the more mathy way of writing your composition, you can
rather write g << f
, but personally, I like reading code left to right.
let readUser () = System.Console.ReadLine () let createGreeting user = sprintf "Hello %s" user let user = readUser () let greeting = createGreeting user let greetUser = readUser >> createGreeting
The first is the imperative way of first calling one function, storing the result, then using the result in a second call and so on. The last line will create a function that does both. In our number guessing game, we use this composition several places.
Let’s define >>
ourselves.
let (>>) f g a = g (f a)
let (>>) f g a = g (f a);; val (>>) : f: ('a -> 'b) -> g: ('b -> 'c) -> a: 'a -> 'c
Another useful function is |>
:
(|>)
(|>);; val it: ('a -> ('a -> 'b) -> 'b)
This makes it simple to chain a result through a set of functions where each function has as input the previous result
let isEven x = (x % 2) = 0 let a = 1 |> isEven |> not let b = 2 |> isEven |> not
let isEven x = (x % 2) = 0 let a = 1 |> isEven |> not let b = 2 |> isEven |> not;; val isEven: x: int -> bool
Defining it ourselves is easy
let (|>) x f = f x
let (|>) x f = f x;; val (|>) : x: 'a -> f: ('a -> 'b) -> 'b
Given the simplicity of the implementation, it’s amazing how useful it is in practice.
The above two constructs lets you easily replace your Linq needs in F# either by
chaining data directly through your process using |>
, or creating a more
generic function using >>
:
[10..200] |> Seq.map ((+) 1) |> Seq.filter (fun x -> x % 2 = 1) |> Seq.skipWhile (fun x -> x < 100) |> Seq.take 3
seq [101; 103; 105]
Yet another is one that deconstructs a tuple while it goes
("Hello", "World!") ||> printfn "First: %s, Second: %s"
("Hello", "World!") ||> printfn "First: %s, Second: %s";; First: Hello, Second: World!
All these have similar functions composing the other way: <<
, <|
etc.
Partial application
We have already looked at partial application. Any time you apply an argument to a function, you create a new function with one less parameter. When the last parameter is bound, the function is called.
As function parameters are bound left to right, it’s important to put the most “static” parameters first, and the data you wish to operate over last. This way you can create a new function by configuring an existing function.
let filterMap f m = Seq.filter f >> Seq.map m let myFunction = filterMap (fun x -> x > 0) (fun x -> -x) myFunction [-5 .. 5] |> List.ofSeq
unit
(void)
In other languages, we think of void
as “does not return a value”. This is a
misnomer, as void
is conceptually a set with exactly one value, a singleton set.
In C#, void
is actually an empty set, so it’s not even possible for us to
create an instance of it, but every time we say return;
we could think of
it as returning the only value of the void
type. return void;
would be a
more correct way of writing it, and the type should be named Void
.
Let’s create a singleton set in C# that we could use instead of void
.
public sealed class Unit { private Unit() {} public static readonly unit = new Unit(); }
Given the above definition, we could write a void function by being a little more explicit
void SomeVoidFunction(/*void*/) { return; // void } void CallingVoid() { return SomeVoidFunction(); } Unit SomeUnitFunction(Unit _) { return Unit.unit; } Unit CallingUnit(Unit _) { return SomeUnitFunction(Unit.unit); }
This is pretty much how it works in F#, although less verbose.
In F# (and other functional languages), the “void” is a regular type in itself,
but with some special syntax. In F# we call the void type unit
, and it has the
value ()
, also called unit, which is the only value. This is why you have to
supply the unit
value when you call a function that “doesn’t accept
arguments”. let f () = 1
actually accepts an argument, but it only accepts the
unit
type, which only has the ()
value. Calling f
with a value will
actually pattern match the argument, and fail if it doesn’t match ()
, which it
will never do as unit
only has a single value. You can write the above
function as
let f () = 1 let g (_ : unit) = 1 let h x = match x with | () -> 1 let i = function | () -> 1
let f () = 1 let g (_ : unit) = 1 let h x = match x with | () -> 1 let i = function | () -> 1;; val i: unit -> int
You have to explicitly state your return value in F# even if its unit
:
let f () = ()
let f () = ();;
Lists and sequences
Sequences in F# uses .NETs IEnumerable
. List
, on the other hand, is an
immutable single-linked list and is not the same as .NETs mutable List
.
Modifying an F# list will return a new list with the modification, leaving the
original intact. This behavior should be familiar as System.String
is
implemented as an immutable structure.
Converting between Seq
, List
and Array
is done using ofX
or toX
, where
X
is one of these types. Seq.toArray
will create an array, while
Seq.ofArray
will convert from an Array
.
Lists have some syntactic sugar for constructing and destructing. [1; 2; 3]
will construct a list of three elements. Notice that ;
is used for separating
elements instead of ,
as the latter is used for tuples.
Arrays use a syntax much like lists: [| 1; 2; 3 |]
.
seq { yield 1 yield 2 yield 3 }
seq [1; 2; 3]
seq { 1 .. 5 }
seq [1; 2; 3; 4; ...]
seq [1; 2]
[1; 2]
Products
Products allow you to store several values together in a single type. A tuple is the simplest product type
(10, "aoeu", 'a')
(10, "aoeu", 'a')
The type will be a * b * ...
for tuples, but the most used tuple is the simple
two-element tuple.
Using just tuples, we can construct anything that requires several fields.
let a = ("Name", 20) let name (name, _) = name let age (_, age) = age let setName newName (_, age) = (newName, age) let setAge newAge (name, _) = (name, newAge) let b = setAge 40 a // ("Name", 40)
let a = ("Name", 20) let name (name, _) = name let age (_, age) = age let setName newName (_, age) = (newName, age) let setAge newAge (name, _) = (name, newAge) let b = setAge 40 a;; val name: name: 'a * 'b -> 'a val age: 'a * age: 'b -> 'b val setName: newName: 'a -> 'b * age: 'c -> 'a * 'c val setAge: newAge: 'a -> name: 'b * 'c -> 'b * 'a
This is tedious (imagine having 10 fields), but fortunately, there are other ways of handling data with multiple fields.
Records will automatically give you a typesafe way of grouping fields.
type Person = { name : string age : int } let a = { name = "Name"; age = 20 } let b = { a with age = 40 } let c = { name = "Name"; age = 40 } let same = b = c // true
true
A very important feature here is equality. F# will automagically create Equals
and GetHashCode
for you. This means equality won’t look at the memory address
by default, reducing a lot of boilerplate code and bugs.
Discriminated Unions (CoProducts / Sums)
These are values that might be one of several cases.
type T = | A | B let a = A let b = B
type T = | A | B let a = A let b = B;; type T = | A | B
They can contain data
type T = | A of int | B of string | C of int * string let a = A 10 let b = B "aoeu" let c = C (10, "aoeu")
type T = | A of int | B of string | C of int * string let a = A 10 let b = B "aoeu" let c = C (10, "aoeu");; type T = | A of int | B of string | C of int * string
Option
is implemented as a union:
type Option<'a> = | Some of 'a | None let a = Some 10 let b = None let c = Some "other thing"
type Option<'a> = | Some of 'a | None let a = Some 10 let b = None let c = Some "other thing";; type Option<'a> = | Some of 'a | None
Iteration
Regular loops works in F#, but we don’t use it much (or at all?) in our
projects. The functional approach uses recursion, but we’ll rely on higher
level functions like fold
most of the time, which is implemented in terms of
the lower-level recursion.
The imperative approach using a mutable variable:
let sum xs = let mutable tot = 0 for x in xs do tot <- tot + x tot sum [1; 10; 100]
111
Using recursion:
let sum xs = let rec go tot = function | [] -> tot | x::xs -> go (tot + x) xs go 0 xs sum [1; 10; 100]
111
Using a fold:
let sum xs = Seq.fold (fun s x -> s + x) 0 xs let a = sum [1; 10; 100] // Or just let sum' = Seq.fold (+) 0 let b = sum' [2; 20; 200] // Scan returns intermediate results let c = List.scan (+) 0 [1 .. 5]
let sum xs = Seq.fold (fun s x -> s + x) 0 xs let a = sum [1; 10; 100] let sum' = Seq.fold (+) 0 let b = sum' [2; 20; 200] let c = List.scan (+) 0 [1 .. 5];; val sum: xs: seq<int> -> int val sum': (int list -> int)
fold
fold
(also called reduce
) is a very useful abstraction that we need examine
more closely.
Seq.fold
Seq.fold;; val it: (('a -> 'b -> 'a) -> 'a -> seq<'b> -> 'a)
'a
is our state, and 'b
is our element. We need a function that takes the
state so far, an element from the sequence, and returns a new state. It’s often
used to aggregate values as with sum
, but as long as we conform with the
types, it can do pretty much everything. It might be useful to see a couple of
examples.
This implements average by using a tuple containing the number of items and the running total as a tuple.
let avg = Seq.fold (fun (c, t) v -> (c+1, t+v) ) (0, 0) >> fun (c, t) -> t/c avg [50..100]
75
Reversing a sequence
let rev = Seq.fold (fun xs x -> x :: xs) [] rev [1 ..5] // returns [5; 4; 3; 2; 1]
Picking the last element
let uncurry f a b = f (a, b) let tryLast = Seq.fold (uncurry (snd >> Some)) None let a = tryLast [] let b = tryLast [1; 2]
let uncurry f a b = f (a, b) let tryLast = Seq.fold (uncurry (snd >> Some)) None let a = tryLast [] let b = tryLast [1; 2];; val uncurry: f: ('a * 'b -> 'c) -> a: 'a -> b: 'b -> 'c val tryLast: (int list -> Option<int>)
Here we count the number of equal elements in a list, and returns a new list with a tuple containing the item and count ordered by the count.
module Option = let getOr x = function | Some v -> v | None -> x let byCount = Seq.fold (fun s k -> Map.tryFind k s |> Option.map ((+) 1) |> Option.getOr 1 |> fun v -> Map.add k v s ) Map.empty >> Map.toSeq >> Seq.sortByDescending snd >> Seq.toList byCount [1; 2; 1; 1; 1; 2; 3]
1 | 4 |
2 | 2 |
3 | 1 |
The usecases are many. Whenever you need to work on a sequence of elements, you
can usually solve it using fold
, though it does take some practice both to
read them, notice that you can use it, and write it. But by the time you’ve
mastered folds, or at least gotten more proficient with them, you’ll miss them
when you only have loops available.
Pattern matching
Pattern matching is a bit like like a combined is
, as
, if
, var
and
switch
on steroids. It allows us to both check the structure of our data, as
well as bind the values contained within. We can using in let bindings, function
definition, match and function expressions (and probably more places).
Deconstructing
type T = A of int let f (A v) = v + 10 let a = f (A 0)
type T = A of int let f (A v) = v + 10 let a = f (A 0);; type T = | A of int
let swap (a, b) = (b, a) let (a, b) = swap ("Hello", 24)
let swap (a, b) = (b, a) let (a, b) = swap ("Hello", 24);; val swap: a: 'a * b: 'b -> 'b * 'a
match
and function
function
exists because we’re lazy and would rather not specify an argument
when it doesn’t help with clarity.
let f x = match x with | Some v -> v | None -> 10 let g = function | Some v -> v | None -> 10
let f x = match x with | Some v -> v | None -> 10 let g = function | Some v -> v | None -> 10;;
We used this in our number guessing game earlier
let answer = 10 let withFunction = [1] |> Seq.exists ((=) answer) |> function | true -> "Correct" | false -> sprintf "The answer was %d" answer let withMatch = [1] |> Seq.exists ((=) answer) |> fun x -> match x with | true -> "Correct" | false -> sprintf "The answer was %d" answer
10
function
only let’s us avoid a bit of boilerplate as it replaces
fun x -> match x with
with just function
.
Mapping
A Functor
is not really an abstraction that exists in F# as the language,
unfortunately, doesn’t contain the necessary abstractions to implement it. The
key point is that it’s some “structure” that we’re able to “skip over” in order
to operate on whatever it abstracts over. The name is quite confusing, but in
practice, it’s anything that implements a function map
.
let a = Option.map let b = Seq.map let c = List.map
let a = Option.map let b = Seq.map let c = List.map;;
Notice that the only thing that differ is the structure; option
, seq
and
list
. In other languages, this can be abstracted away, but in F#, you have to
specify the structure you’re mapping over and leak some implementation details.
Let’s call this structure f
and rewrite the definition of map:
map : (a -> b) -> f<a> -> f<b>
. Then let’s look at C#s Select
and rewrite it
a bit.
public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector ) public static IEnumerable<B> Select<A, B>( this IEnumerable<A> source, Func<A, B> selector ) public static F<B> Select<A, B>( this F<A> source, Func<A, B> selector )
After rewriting, it’s pretty clear that we have Select : F<A> -> (A -> B) ->
F<B>
, which is the same as our F# function map
, but with the parameters
reversed. So F#s map
is actually Select
. The reason the parameters is
swapped is that we put configuration parameters first, and the data we operate
over last. This allows us to easily compose the functions. In C#, we use the .
syntax, so it makes sense taking the thing we operate on as the first argument.
Many of the operations of Linq is easy to find in F#, but here are some with somewhat different names:
C# | F# |
---|---|
Where | filter |
Select | map |
SelectMany | collect |
Single | exactlyOne |
All | forall |
Any | exists |
As noted in the beginning of this section, F# don’t have the abstraction to
support Functors
. By this I mean that we cannot have a single map
function
that Option
, List
and other structures can implement. We could use
interfaces, but then we would be forced to use OOP, and Option
and List
are
modules, not objects.
This limitation is quite obvious when we need to refactor from Seq
to List
for instance, or when we have nested structures. The following would be a list
of options in F# – notice the use of our mapping functions
let maybeMap f = List.map (Option.map f) maybeMap ((*) 2) [Some 2; None; Some 5]
Some | 4 | None | Some | 10 |
And the same function in Haskell:
let maybeMap = fmap . fmap maybeMap (2 *) [Just 2, Nothing, Just 5]
Because both Maybe
(Haskells Option
) and List
implements Functor
which
contains the mapping function fmap
(F#s map
), we can compose these. Our
maybeMap
is actually not just mapping Maybe
inside List
, but any structure
contained within any other structure. Look at the type structure below where we
have the structures f
and g
.
mapInner :: (f (g a) -> f (g b)) -> f (g a) -> f (g b) mapInner = fmap . fmap