UP | HOME

F# introduction for my coworkers
A quick, dirty, and mostly wrong introduction to F#

Table of Contents

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.

1 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
..

2 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.

2.1 Visual Studio Code

Use Ionide. This is probably the easiest to use, although I've not used it myself.

2.2 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.

2.3 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.

2.4 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.

2.5 vim

I've not yet tried using F# from vim.

3 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.

4 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.

5 .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.

6 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.

6.1 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
printf "Top: %d, SomeModule: %d, someFunction: %d" p SomeModule.p (SomeModule.someFunction ())
Top: 1, SomeModule: 2, someFunction: 4
val p : int = 1
module SomeModule = begin
  val p : int = 2
  val someFunction : unit -> int
end
val it : unit = ()

PS: I don't encourage using lots of shadowing.

6.2 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.

6.3 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
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`` () = ()
val ( When calling SomeFunction with some special state throws exception ) :
  unit -> unit

This will make it a lot simpler to read test results.

6.4 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
val f : a:int -> b:int -> int
val g : a:int -> b:int -> int
val h : a:int -> b:int -> int

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
val f : a:int -> b:int -> int
val g : a:int -> b:int -> int
val h : a:int -> b:int -> int
val i : a:int -> b:int -> int
val j : 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
val f : x:'a -> 'a

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

6.5 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.

6.6 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
val res : int = 1

The last expression will be the result. This is true for all expressions.

6.7 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;}
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.

7 A simple Number Guessing Game

printfn "Hello, World"
Hello, World
val it : unit = ()

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.

7.1 Walk-through

7.1.1 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.

7.1.2 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.

7.1.3 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.

7.1.4 [<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
val a : int = 1
val b : int = 1
val c : int = 1
val d : int = 1

7.1.5 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
val tryWithin : a:'a -> b:'a -> v:'a -> 'a option when 'a : comparison

7.1.6 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

7.1.7 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.

7.1.8 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
val it : seq<int> = 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.

7.1.9 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
val it : arg00:int -> System.Random = <fun:clo@8>

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.

7.1.10 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 = <fun:it@101-5>

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"

7.2 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.

8 Literals

In order to be a CLR compile time constant, we have to annotate our binding.

let notReallyALiteral = 1

[<Literal>]
let CompileTimeConstant = 1
val notReallyALiteral : int = 1
val CompileTimeConstant : int = 1

9 Functions

9.1 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.

9.1.1 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 
val ( +* ) : a:int -> b:int -> int
val a : int = 9
val b : int = 9

Use the REPL to look at how common functions are defined.

9.1.2 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)
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
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

9.1.3 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
val f : a:int -> b:int -> c:int -> int
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
val f : a:int -> b:int -> c:int -> int
val g : (int -> int -> int)

g here is now defined as let g b c -> 10 + b + c. This is called partial function application.

9.2 Composition

(>>)
val it : (('a -> 'b) -> ('b -> 'c) -> 'a -> 'c) = <fun:it@67-6>

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]
val it : int list = [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)
val ( >> ) : f:('a -> 'b) -> g:('b -> 'c) -> a:'a -> 'c

Another useful function is |>:

(|>)
val it : ('a -> ('a -> 'b) -> 'b) = <fun:it@78-7>

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
val isEven : x:int -> bool
val a : bool = true
val b : bool = false

Defining it ourselves is easy

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
val it : seq<int> = seq [101; 103; 105]

Yet another is one that deconstructs a tuple while it goes

("Hello", "World!")
||> printfn "First: %s, Second: %s"
First: Hello, Second: World!
val it : unit = ()

All these have similar functions composing the other way: <<, <| etc.

9.3 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
val filterMap : f:('a -> bool) -> m:('a -> 'b) -> (seq<'a> -> seq<'b>)
val myFunction : (int list -> seq<int>)
val it : int list = [-1; -2; -3; -4; -5]

10 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
val f : unit -> int
val g : unit -> int
val h : x:unit -> int
val i : unit -> int

You have to explicitly state your return value in F# even if its unit:

let f () = ()
val f : unit -> unit

11 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
}
val it : seq<int> = seq [1; 2; 3]

seq { 1 .. 5 }
val it : seq<int> = seq [1; 2; 3; 4; ...]

seq [1; 2]
val it : seq<int> = [1; 2]

12 Products

Products allow you to store several values together in a single type. A tuple is the simplest product type

(10, "aoeu", 'a')
val it : int * string * char = (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)
val a : string * int = ("Name", 20)
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
val b : string * int = ("Name", 40)
val it : string = "ob-fsharp-eoe"

>

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
type Person =
  {name: string;
   age: int;}
val a : Person = {name = "Name";
                  age = 20;}
val b : Person = {name = "Name";
                  age = 40;}
val c : Person = {name = "Name";
                  age = 40;}
val same : bool = true
val it : string = "ob-fsharp-eoe"

>

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.

13 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
val a : T = A
val b : T = 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
val a : T = A 10
val b : T = B "aoeu"
val c : T = C (10,"aoeu")

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
val a : Option<int> = Some 10
val b : Option<'a>
val c : Option<string> = Some "other thing"

14 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]
val sum : xs:seq<int> -> int
val it : int = 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]
val sum : xs:int list -> int
val it : int = 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]
val sum : xs:seq<int> -> int
val a : int = 111
val sum' : (int list -> int)
val b : int = 222
val c : int list = [0; 1; 3; 6; 10; 15]

val sum : xs:seq<int> -> int
val it : int = 111

14.1 fold

fold (also called reduce) is a very useful abstraction that we need examine more closely.

Seq.fold
val it : (('a -> 'b -> 'a) -> 'a -> seq<'b> -> 'a) = <fun:clo@166>

'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]
val avg : (int list -> int)
val it : int = 75

Reversing a sequence

let rev = Seq.fold (fun xs x -> x :: xs) []
rev [1 ..5]
val rev : (int list -> int list)
val it : int list = [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]
val uncurry : f:('a * 'b -> 'c) -> a:'a -> b:'b -> 'c
val tryLast : (int list -> int option)
val a : int option = None
val b : int option = Some 2

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]
module Option = begin
  val getOr : x:'a -> _arg1:'a option -> 'a
end
val byCount : (int list -> (int * int) list)
val it : (int * int) list = [(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.

15 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).

15.1 Deconstructing

type T = A of int
let f (A v) = v + 10
let a = f (A 0)
type T = | A of int
val f : T -> int
val a : int = 10

let swap (a, b) = (b, a)
let (a, b) = swap ("Hello", 24)
val swap : a:'a * b:'b -> 'b * 'a
val b : string = "Hello"
val a : int = 24

15.2 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 | Nothing -> 10
let g = function | Some v -> v | Nothing -> 10
val f : x:Option<int> -> int
val g : _arg1:Option<int> -> int

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
val answer : int = 10
val withFunction : string = "The answer was 10"
val withMatch : string = "The answer was 10"

function only let's us avoid a bit of boilerplate as it replaces fun x -> match x with with just function.

16 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
val a : (('a -> 'b) -> 'a option -> 'b option)
val b : (('a -> 'b) -> seq<'a> -> seq<'b>)
val c : (('a -> 'b) -> 'a list -> 'b list)

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]
val maybeMap : f:('a -> 'b) -> ('a option list -> 'b option list)
val it : int option list = [Some 4; null; 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

Date: 2017-10-16 Mon 00:00

Author: Simen Endsjø

Created: 2017-10-25 Wed 21:29

Validate