Saturday, January 23, 2016

The Book of F#, Chapter 3: Fundamentals

In some sense, Chapter 3 is the first ‘real’ chapter of this book. In it, we are given a whirlwind tour of a large number of F# facets, some more interesting than others.

What is clear to me though is that you need a decent knowledge of .NET to really understand the content, especially in the sections on Generics and Exceptions.


Immutability and Side Effects

If you’ve studied functional programming before, there will be no surprises here: bindings are immutable by default, and side-effects (in general) need to be explicitly permitted.

I think this is great for two reasons. The first is the usual one: once something is assigned to, you know that its value won’t ever change and so reasoning about it is much easier. The second is more architectural: you end up putting the right things in the right places. If a quantity needs changing after it’s been created, is it really the same quantity?

A basic example would be the Builder Pattern from the object-oriented world. That is the antithesis of immutability and gives you the option to muck around with your object as much as you like, even after construction! There are plenty of ways to have immutable objects in C#, especially now we have getter-only autoprops in C# 6.0, but having a language that won’t let you do it another way is so much nicer!

Another point, which the author raises on this topic, is thread-safety: if something can be changed and can also be accessed simultaneously by multiple threads, who knows when and by what it might be mutated.


Functional Purity

Having said the above about side-effects and immutability, there’s a bombshell: F# is impure! Shock horror for a language that has access to the whole .NET Framework.

What this basically means is that whilst the default case is for functions to be pure, that is to consist of expressions that always return a value with no side effects, you can ask F# to allow variables to be mutated.

Whilst this allows you to write code a lot more easily, you lose the ability to fully reason about your functions as they lose idempotence. I’m taking the decision now that this sort of mutation is bad, and will try and do as little as possible of it! In cases where it seems as though some kind of application-level state is required, I’ll do my best to replace the stateful behaviour with something else.


Bindings

These are how F# determines values or executable code.

The first is the let binding, which associates a name with a value and seems very flexible — you can bind an int, string, function or result with let. let bindings are immutable and are treated like readonly in C#, not as constants. Compile-time constants are instead declared with [<Literal>]. This seems slightly strange: what was wrong with the const modifier?

If you want to be naughty and change your variables, you can allow with the mutable keyword, changing the value with <-. As of F# 4.0, we can also use the mutable keyword for values that change inside closures. Why you would ever want this is beyond me, though!

Now for a slightly more complex concept, the reference cell. A reference cell, which you might declare with let cell = ref 0 encapsulates a mutable variable which we access using ! and change using :=. That’s what the book says, anyway. I still don’t quite get it, in particular why you would want to use it, especially when you consider that when you do something like (example from here):

let cell = ref 0
let cell2 = cell
cell2 := 1

then the value of cell also becomes 1! To me this is confusing and potentially dangerous.

Secondly, we have the use binding: this is similar to using in C# in the sense that it’s for things that implement IDisposable, and those things get disposed automatically when they go out of scope. It’s also worth noting that you can’t use them inside a Module and will get a warning saying that they are treated as let bindings in this context. Essentially this is because modules are treated as static classes that are always in scope.

You can also write using as in C#, and it’s actually more powerful than the C# as things in F# always return a result, so you can assign to variables directly from the using block rather than inside it.

Finally, do bindings execute code outside of a function of value context. This seems to be useful when doing things like initialisation and constructor calling, and I’m sure plenty of other scenarios that I haven’t yet come across


Identifier Naming

This one’s short: we can enclose a string in "", which is helpful for unit testing as we can say things like let "MethodName_StateUnderTest_ExpectedBehaviour" = ....


Core Data Types

This is really just a list of syntactic differences between F# and C#:

  • Uses not rather than ! for boolean negation.
  • Numeric types are the same as C# with some abbreviation and suffix changes.
  • There is a new operator!! ** is the exponent operator, which seems very handy for mathematical programs: Math.Sqrt(Math.Pow(x,2.0)+Math.Pow(y,2.0)) no more!
  • We also have&&&, |||, ^^^, ~~~, <<<, >>> for bitwise AND, OR, XOR, negation, left shift, right shift.
  • There are no implicit type conversions for numerics as they are considered side effects, so we can’t divide a float by an int and expect it to work.

Type Inference

The type inference capabilities of F# are such that newcomers often mistake it for a dynamically typed language. C# also has type inference (var) but it is very limited.

I think this is probably far too detailed a topic to go into now, but if we copy the example from the book, the upshot is that creating a person with three properties in F# requires exactly three explicit type declarations:

type Person (id : System.Guid, name : string, age : int) =
member x.Id = id
member x.Name = name
member x.Age = age

This appeals to my DRY sense of (humour) programming. We already know the age is an int, why should we have to tell the computer that in both the constructor and the property definition?

You can add a type annotation as well if the compiler gets confused, so something like let x : float = 2.0 .


Nullability

Null is almost never used in F#. To use it, include the [<AllowNullLiteral>] attribute. It’s only really there for .NET interop. Overall this should reduce NullReferenceException instances and null checks.

All this is fantastic. Even with the addition of the null conditional operator in C# 6.0 (represented either by ?. or ?[ depending on if you are accessing a member or an indexer), it’s still annoying to have to ask if something has a null reference all the time.

F# also caters for stuff that might genuinely not have a value: it has the type Option<'T> which is a discriminated union of Some('T) and None, and has its own keyword (e.g. let middleName : string option = None).

This falls through to functions as well: an optional parameter is denoted with ? and given a default value with defaultArg. The defaultArg function accepts an option as its first argument and returns its value when it is Some< _ >; otherwise, it returns the second argument. I’m not so sure I like this syntax as much as the C# version where you just write argName = defaultValue in the method signature, that seems cleaner than specifying this:

type T(?arg) =
  member val Arg = defaultArg arg 0

Lastly, for things that evaluate only for a side effect, there is the unit type, whose value is written as (). This is a concrete type that signifies that no particular value is present, so the result of any expression can safely be ignored.

If we get back something that’s not unit and we don’t use it (bind it or pass to another function), we get a compiler warning. This can be removed by piping (|>) the result to ignore.


Flow Control

The author is quick to point out that recursion is the preferred looping mechanism in F#, but that there are imperative constructs as well:

While loops, written as while...do. As a notable aside, the body of the loop must return unit, reinforcing the point that expressions return values!

For loops:

for i = 0 to 100 do... — standard for loop
for i = 100 downto 100 do... — making it count downwards

for i in [1..10] do... — the equivalent of foreach in C#, this works on enumerated types. What surprised me was that this is actually a syntactic shortcut for pattern matching. I am presuming that this is translated to something that matches on i and does nothing if it is less than 0 or greater than 10. I’m going to skip the details of pattern matching for now as it’s covered in later chapters.

Note that we don’t have break orcontinue keywords to force early termination of loops. This is strange and will definitely require some care when creating certain kinds of loop.

Branching

This is slightly different to C# and uses the syntax if ... then ... else, or if...then...elif...then...else. The nice thing about them is that because they return a value, we don’t need to either pre-assign a variable or use a ternary operator, e.g.

// Using a pre-assigned variable;
bool found;
if(condition)
{
  found = true;
}
else
{
  found = false;
}

// Using ternary operator
bool found = condition ? true : false;

In F# this would be something like

let found condition = 
  if condition then
    true
  else
    false

This is a contrived example and so actually looks a bit messy in F#, but I can see how this works well for more complex branches. Another point to note is that when only an if branch is specified it must evaluate to unit, otherwise each branch must evaluate to the same type.


Generics

As a C# dev, I love generics. Thankfully, their creator also happens to be the F# language designer, Don Syme, and so F# gets first-class support for them too.

Generics allow type-safe code that works with multiple data types, generating implementations based on the input type — consider the difference between ArrayList in which everything is System.Object and List<'T> where the type is clearly disambiguated.

In F#, generics are named with a leading apostrophe ('a, 'A, 'TInput). F# prefers automatic generalisation e.g.

let toTriple a b c = (a, b, c);;
val toTriple : a:'a -> b:'b -> c:'c -> 'a * 'b * 'c

To do useful things with the types, we need constraints (written as when). We inherit all the ones from C# (ref types, value types, those with a default constructor, those that derive from a class or interface) but also F# adds a few more. There’s loads of examples in the book so I’m only going to copy a few in here.

// Fairly standard constraint from C#: we need a parameterless constructor
let myFunc (stream : 'T when 'T : (new : unit -> 'T)) = ()

// Interview question alert!! This works only on comparable types
// which is of course the constraint on sorting algorithms.
let myFunc (stream : 'T when 'T : comparison) = ()

There are a couple of other syntax elements to look at: flexible types are a shortcut for subtype constraints, and are denoted by #: let myFunc (stream : #System.IO.Stream) = ()

To use a generic type as a parameter and let the compiler infer the type, use <_ >: let printList (l : List<_>) = l |> List.iter (fun i -> printfn "%O" i). This will print the result of evaluating the ToString method on whatever type is passed in the list.

One final thing that is specific to F# is the ability to statically resolve generics. These are resolved at compile time and are primarily for use with inline functions (that is, those that are directly integrated into the calling code and/or custom operators. The implication is that the compiler generates a version of the generic type for each resolved type rather than a single version, so be warned!

We declare these using ^ rather than an apostrophe, for example:

// All types where null is valid
let inline myFunc (a : ^T when ^T : null) = ()

// Custom operator, resolves at compile time to ensure all types
// that use it have a Pow operator implementation
let inline (!**) x = x ** 2.0

Overall, it seems like generics are pretty powerful in F#, which is a bonus to me.


When Things Go Wrong

There are two exception constructs in F#: try...with and try...finally (i.e. we have no try...with...finally block). If you need both then use a try...with within a try...finally.

The exception is captured with as and will pattern match against the type of exception, which we do with :?. In total, this gives us something like:

open System.IO

try
  use file = File.OpenText "somefile.txt"
  file.ReadToEnd() |> printfn "%s"
with
| :? FileNotFoundException as ex ->
  printfn "%s was not found" ex.FileName
| :? PathTooLongException
| :? ArgumentNullException
| :? ArgumentException ->
  printfn "Invalid filename"
| _ -> printfn "Error loading file"

My first impression is that this is a more terse and compact block than the C# equivalent, and still very readable even to someone new to the langauge.

To partially handle an exception we use raise or reraise — the former loses the stacktrace and so probably isn’t that desirable, but the latter is only valid in a with block, so both are required depending on the exact circumstance.

Return values com up again here: try...with returns a value. You can return things other than unit, but all cases must then return the same type. Commonly we use the Maybe monad in this case (i.e. the DU of Some<_> and None).

We also raise our own exceptions with raise, but there are additional syntactic helpers: failwith and failwithf , which take in strings as the exception’s message:

if not (File.Exists filename) then
  failwith "File not found"

if not (String.IsNullOrEmpty filename) then
 failwithf "%s could not be found" filename

We can create our own exceptions by deriving from exn, which is shrothand for System.Exception, or use the incredibly lightweight syntax provided by the exception keyword:

exception RetryAttemptFailed of string * int
exception RetryCountExceeded of string

Summary

There was a lot of syntax to take in as part of this chapter. I will admit that most of it didn’t register the first time, but having come back to it whilst writing this post, a lot more makes sense.

The things that stuck with me were default immutability and type inference. Both features seem incredibly powerful if harnessed correctly, so I guess I have to learn how to do that next!

Next time, I’m going to be covering Chapter 5 (Let’s Get Functional). This is because the contents of Chapter 4 cover object-oriented programming in F#, and I’d rather get functional-first thinking drilled into my head before learning how to do OO stuff in another language. Hopefully this will keep my brain progressing down the road towards functional thinking!

No comments:

Post a Comment