- Testing with F#
- Mikael Lundin
- 2320字
- 2021-07-23 20:46:13
Immutability
The default behavior of F# when using the let
keyword is an immutable value. The difference between this and a variable is that the value is not possible to change. With this, there are the following benefits:
- It encourages a more declarative coding style
- It discourages side effects
- It enables parallel computation
The following is an example implementation of the String.Join function using a mutable state:
// F# implementation of String.Join let join (separator : string) list = // create a mutable state let mutable result = new System.Text.StringBuilder() let mutable hasValues = false // iterate over the incoming list for s in list do hasValues <- true result .Append(s.ToString()) .Append(separator) |> ignore // if list hasValues remove last separator if hasValues then result.Remove(result.Length - separator.Length, separator.Length) |> ignore // get result result.ToString()
In this example, we used a string builder as a state where values were added together with a separator string. Once the list was iterated through, there was one trailing separator that needed to be removed before the result could be returned. This is of course true only if the sequence was nonempty.
This is how it could be written in an immutable way:
// F# implementation of String.Join let rec join separator = function | [] -> "" | hd :: [] -> hd.ToString() | hd :: tl -> hd.ToString() + separator + (join separator tl)
This implementation is quite stupid, but it is proving the point of immutability. Instead of having a state that is mutated, we used recursion to apply the same method once again with different parameters. This is much easier to test, as there are no moving parts within the function. There is nothing to debug, as there is no state that is changing.
If one were to create an expressive solution to this problem, it would rather look like this:
// F# implementation of String.Join let join separator = // use separator to separate two strings let _separate s1 s2 = s1 + separator + s2 // reduce list using the separator helper function List.reduce _separate
In this implementation, we add a few constraints by narrowing down the specification. Only a list of strings are now allowed, and the implementation will throw a System.ArgumentException
exception when it encounters an empty list. This is OK if it's a part of the specification.
The problem itself is a reduce
problem, so it is natural to use the higher order reduce
function to solve it. All of this was, of course, a matter of exercise. You should always use the built-in String.Join function and never roll your own.
This is where you can see the functional programming excel. We moved from a 20 Lines of Code (LOC) mutable code example to a 3 LOC immutable code example.
Less code makes it easier to test, and less code may also reduce the need for testing. Each line of code brings complexity, and if we can bring down the number of lines of code by writing terser code, we also reduce the need for testing.
Immutable data structures
As we have seen, the immutable value is default in F# compared to the mutable variable in other languages. But the statement that F# makes on immutability doesn't end here. The default way of creating types in F# makes for immutable types.
An immutable type is where you set values upon creation of an instance, and each attempt to modify the state of the instance will result in a new instance. The most comprehensive example in .NET is DateTime
.
This makes it possible for us to use function chaining like this:
// Now + 1 year + 1 month + 1 day > System.DateTime.Today.AddYears(1).AddMonths(1).AddDays(1.)
In F#, we define a new type like this:
> type Customer = { FirstName : string; Surname : string; CreditCardNumber: string } > let me = { FirstName = "Mikael"; Surname = "Lundin"; CreditCardNumber = "1234567890" }
Now if I update the credit card number, it generates an new instance.
let meNext = { me with CreditCardNumber = "2345678901" }
There are many benefits of doing this:
- If you are processing the
me
parameter, it will not change its state, makingasync
operations safe. - All data that belongs to the
me
parameter is accurate at the point in time when theme
parameter was created. If we changeme
, we lose this consistency.
Fewer moving parts also make it easier to test code, as we don't have to care about the state and can focus on the input and output of functions. When dealing with systems such as trading and online ordering, immutability has become such a major player that now there are immutable databases. Take a look at Datomic and validate how an immutable database fits into immutable code.
Built-in immutable types
In order to support immutability throughout the language, there are some immutable types that come with the F# framework. These support pretty much any kind of functional computation that you would need, and with them, you can compose your own immutable type.
Most of the already defined types and data structures within the .NET framework are mutable, and you should avoid them where you can and sometimes create your own immutable versions of the functionality. It is important to find a balance here between cost versus value.
Tuple is one of the most common data types with the F# language and is simply a way of storing two or several values in one container.
The following code is an example of a tuple:
// pattern matching let tuple = (1, 2) let a, b = tuple printfn "%d + %d = %d" a b (a + b)
A tuple is immutable because once it's created, you cannot change the values without creating a new tuple.
Another important aspect of the tuple is how it maps to the out
keyword of C#. Where the .NET framework supports out variables, it will be translated into a tuple in F#. The most common usage of the out
keyword is the Try
pattern, as follows:
// instead of bool.TryParse // s -> bool choice let parseBool s = match bool.TryParse(s) with // success, value | true, b -> Some(b) | false, _ -> None
The code maps the bool.TryParse
functionality into the choice
type, which becomes less prone to error as it forces you to handle the None
case, and it only has three combinations of values instead of four, as with the tuple result.
This can be tested as follows:
[<Test>] let ``should parse "true" as true`` () = parseBool "true" |> should equal (Some true) [<Test>] let ``should parse "false" as false`` () = parseBool "false" |> should equal (Some false) [<Test>] let ``cannot parse string gives none`` () = parseBool "FileNotFound" |> should equal None
The list
type is a very used collection type throughout F#, and it has very little in common with its mutable cousin: the System.Collections.Generic.List<T> type. Instead, it is more like a linked list with a closer resemblance to Lisp.
The following image shows the working of lists:

The immutable list of F# has the following properties:
- Head
- IsEmpty
- Item
- Length
- Tail
This is enough to perform most computations that require collections. Here is an example of a common way to build lists with recursion:
// not very optimized way of getting factors of n let factors n = let rec _factors = function | 1 -> [1] | k when n % k = 0 -> k :: _factors (k - 1) | k -> _factors (k - 1) _factors (n / 2)
One strength of the list
type is the built-in language features:
// create a list > [1..5];; val it : int list = [1; 2; 3; 4; 5] // pattern matching let a :: b :: _ = [1..5];; val b : int = 2 val a : int = 1 // generate list > [for i in 1..5 -> i * i];; val it : int list = [1; 4; 9; 16; 25]
Another strength of the list
type is the list module and its higher order functions. I will give an example here of a few higher order functions that are very powerful to use together with the list
type.
The map
is a higher order function that will let you apply a function to each element in the list:
// map example let double = (*) 2 let numbers = [1..5] |> List.map double // val numbers : int list = [2; 4; 6; 8; 10]
The fold
is a higher order function that will aggregate the list
items with an accumulator value:
// fold example let separateWithSpace = sprintf "%O %d" let joined = [1..5] |> List.fold separateWithSpace "Joined:" // val joined : string = "Joined: 1 2 3 4 5"
The nice thing about the fold
function is that you can apply it to two lists as well. This is nice when you want to compute a value between two lists.
The following image is an example of this, using two lists of numbers. The algorithm described is called Luhn and is used to validate Swedish social security numbers. The result of this calculation should always be 0 for the Social Security Number (SSN) to be valid:

Here is a perfect situation where you want to compute a value between two lists:
// fold2 example let multiplier = [for i in [1..12] -> (i % 2) + 1] // 2; 1; 2; 1... let multiply acc a b = acc + (a * b) let luhn ssn = (List.fold2 multiply 0 ssn multiplier) % 10 let result = luhn [1; 9; 3; 8; 0; 8; 2; 0; 9; 0; 0; 5] // val result : int = 0
The partition
is an excellent higher order function to use when you need to split values in a list into separate lists:
// partition example let overSixteen = fun x -> x > 16 let youthPartition = [10..23] |> List.partition overSixteen // val youthPartition : int list * int list = // ([17; 18; 19; 20; 21; 22; 23], [10; 11; 12; 13; 14; 15; 16])
The reduce
is a higher order function that will not use an accumulator value through the aggregation like the fold
function, but uses the computation of the first two values as a seed.
The following code shows the reduce
function:
// reduce example let lesser a b = if a < b then a else b let min = [6; 34; 2; 75; 23] |> List.reduce lesser
There are many more useful higher order functions in the List
module that are free for you to explore.
Sequence in F# is the implementation of the .NET framework's IEnumerable
interface. It lets you get one element at a time without any other information about the sequence. There is no knowledge about the size of the sequence.
The F# sequence is strongly typed and quite powerful when combined with the seq
computational expression. It will let us create unlimited length lists just like the yield keyword in C#:
// For multiples of three print "Fizz" instead of the number // and for multiples of five print "Buzz" // for multiples of both write "Fizzbuzz" let fizzbuzz = let rec _fizzbuzz n = seq { match n with | n when n % 15 = 0 -> yield "Fizzbuzz" | n when n % 3 = 0 -> yield "Fizz" | n when n % 5 = 0 -> yield "Buzz" | n -> yield n.ToString() yield! _fizzbuzz (n + 1) } _fizzbuzz 1
I'm sure you recognize the classic recruitment test. This code will generate an infinite sequence of fizzbuzz
output for as long as a new value is requested.
The test for this algorithm clearly shows the usage of sequences:
[<Test>] let ``should verify the first 15 computations of the fizzbuzz sequence`` () = fizzbuzz |> Seq.take 15 |> Seq.reduce (sprintf "%s %s") |> should equal "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizzbuzz"
Creating an immutable type
When you set out to create your own types, you have great tools that will help you start out with immutable versions. The most basic type declaration is the discriminated union:
// xml representation type Node = | Attribute of string * string | Element of Node list | Value of string
A basic type is that of a record, which is in its basic setting an immutable type:
let quote = Element [ Attribute ("cite", "Alan Turing") Value "Machines take me by surprise with great frequency" ]
In order to change the value of an immutable type, you need to create a new copy of it with the value changed, a true immutable type:
// <blockquote cite="Alan Turing">Machines take me by surprise with great frequency</blockquote>
Once in a while, you need to create a class, and this is when you need to be careful to create an immutable type and not a mutable one. This can be identified by the following properties:
- State can only be set upon creation
- Change of state returns a copy of the instance
- The class can only have references to other immutable types
This is an example of a good immutable class type:
type Vector = | X | Y | Z type Point(x : int, y : int, z : int) = // read-only properties member __.X = x member __.Y = y member __.Z = z // update member this.Update value = function | X -> Point(value, y, z) | Y -> Point(x, value, z) | Z -> Point(x, y, value) override this.Equals (obj) = match obj with | :? Point as p -> p.X = this.X && p.Y = this.Y && p.Z = this.Z | _ -> false override this.ToString () = sprintf "{%d, %d, %d}" x y z
When updating any value, it will generate a new instance of Point
instead of mutating the existing one, which makes for a good immutable class.
- JavaScript全程指南
- SOA實(shí)踐
- TypeScript Blueprints
- 前端跨界開(kāi)發(fā)指南:JavaScript工具庫(kù)原理解析與實(shí)戰(zhàn)
- Leap Motion Development Essentials
- C語(yǔ)言程序設(shè)計(jì)(第2版)
- 青少年軟件編程基礎(chǔ)與實(shí)戰(zhàn)(圖形化編程三級(jí))
- Expert Data Visualization
- ASP.NET Core 2 Fundamentals
- Android項(xiàng)目實(shí)戰(zhàn):手機(jī)安全衛(wèi)士開(kāi)發(fā)案例解析
- C語(yǔ)言程序設(shè)計(jì)
- Cocos2d-x Game Development Blueprints
- App Inventor少兒趣味編程動(dòng)手做
- Angular Design Patterns
- 軟硬件綜合系統(tǒng)軟件需求建模及可靠性綜合試驗(yàn)、分析、評(píng)價(jià)技術(shù)