Sunday, January 17, 2016

The Book of F#, Chapter 1: Meet F#

This chapter is really covering the basic points about how to lay out an F# file, and what the language does at a high level. That said, there are some pretty big differences from C# that are worth pointing out.

As well as being a functional-first language, F# is a full-featured OO language with some imperative constructs also available. I’m not sure whether this will turn out to be something that I like or not, but it’s worth bearing in mind.

Files and (lack of) folders

In terms of immediately noticeable things, there are a couple of quirks with the project hierarchy: there are no folders, and files are executed in the order in which the appear in the project. Thus, dependent items must be declared in files below their dependencies.

The benefit of this is that the compiler can infer much more about your code and give unrivalled type inference. It also avoids recursive definitions. Both of these seem like good things! I haven’t been caught out by the execution order yet but I’m sure that day will come.

Though not explicitly stated in the book, it also seems to encourage the user to create small projects . Imagine a project that has 5 folders, each with 5 source files in — a fairly common occurrence in C#, but the idea of having 25 files in a single-level hierarchy and making sure they are in the right order scares me.

Whitespace matters

Whitespace is also significant - code inside a block must be further indented than the opening line. I’ve found this to be annoying, to be honest. I can understand the reasoning behind it (no curly braces!!) but I’m just not used to it yet. This is especially obvious when pasting in code snippets from the book: having to change the indentation of the block block can be cumbersome. A simple example of this nesting from the book is:

let isEven number =
  if number % 2 = 0 then
    printfn "%i is even" number
    printfn "%i is odd" number

We can group by namespaces and modules. Namespaces look pretty much as they don in C#. To my eyes, a module looks analogous to a C# class, but I might prove to be mistaken by the end of the book!

It returns results!

The next important point is that F# is an expression-based language; nearly everything that is evaluated returns a result. This contrasts with C# where only non-void methods do so! One corollary of this is that basic blocks such as if statements need to be looked at in a different light: they return a result! Going back to the previous code snippet, it means that we can write the routine as:

let testNumber = 10
let evenOrOdd = if testNumber % 2 = 0 then "even" else "odd"
Console.WriteLine evenOrOdd

In this simple example we could have done the same in C# using the ternary conditional operator

var evenOrOdd = (testNumber % 2 == 0) ? "even" : "odd";```

but that’s certainly not so in more complex scenarios.

Entry Points

Getting into an application is a bit different too: we define an [<EntryPoint>], rather than a Main method in C#. This is a bit nicer for me than C# as it gives more control over naming:

let MyProgram argv =
  // do some stuff

It’s also worth noting that here, as everywhere else in F#, the compiler assumes that the last expression evaluated is the return value. This means we don’t need to use a return keyword. I’m undecided on this one yet, as I think that using return makes it really obvious when you want to exit a routine.

The Reverse Polish Notation calculator

The chapter finishes with a great example that showcases some of the most powerful features of F#: pattern matching, concise syntax, type inference, and recursion. It introduces a basic parser that can take a string defined in Reverse Polish Notation and perform the required computation.

This would normally be done by creating a mutable stack and pushing/popping from it:

  • split the string into a list of characters;
  • push characters onto the stack until we see something that looks like an operator;
  • when we get an operator, pop twice from the stack (these will be numbers);
  • apply the operator to thee two numbers;
  • push the result onto the stack;
  • repeat until our input list is empty;
  • pop the last remaining number from the stack, which is our result.

I’m not going to write the C# code for this, but it would no doubt require an Enum for the operators, a Stack<int>, a while loop, and some other stuff. What I will do is copy the code from the book (which can be found from the book’s homepage if you want to dig into them:

module TheBookOfFSharp.RpnCalculator

open System

let evalRpnExpr (s : string) =
  let solve items current =
    match (current, items) with
    | "+", y::x::t -> (x + y)::t
    | "-", y::x::t -> (x - y)::t
    | "*", y::x::t -> (x * y)::t
    | "/", y::x::t -> (x / y)::t
    | _ -> (float current)::items
  (s.Split(' ') |> Seq.fold solve []).Head

No stack, no loop, no enum.

It’s different, for sure, and if you’re new to F# and functional programming in general I bet you’ve got no idea what’s going on!

Let’s say the input to this function is the string "4 2 5 * + 1 3 2 * + /". The first thing that happens is it gets turned into an array of characters via (s.Split(' ').

So far, so C#. What happens next is a bit trickier to read: the array that we’ve just created gets sent to the function Seq.fold as its third parameter (the function solve is the first, the empty list [] is the second).

What this will do is recursively call solve taking in a list called items (think of this as our stack!) and a single value from our original array of characters, and do this until there’s nothing left. The empty list just tells it what to initialise items to.

Lastly, the solve function does the business. It uses pattern matching (match) to say that: if we have an operator (one of "+", "-", "*", "/") as the incoming current character, and if the items list has at least two things in it (i.e. it is of the form y::x::t), then remove the first two things in items, apply the operator to them, and stick the result back on the front. In the case that we don’t match this pattern (i.e. we have a number), just stick that in items.

Running through our example, we will therefore:

  • Put 4, 2 and 5 into items
  • Take 5 and 2 out, multiply them (*), and put the result (10) in
  • Take 10 and 4 out, add them (+), and put the result (14) in
  • Put 1, 3 and 2 in
  • Take 2 and 3 out, multiply them (*), and put the result (6) in
  • Take 6 and 1 out, add them (+), and put the result (7) in
  • Take 7 and 14 out, divide them (*), and put the result (2) in
  • Finally, the Head command tells us to take the first things out of items: our answer, 2. (It works!!!)

Writing it in prose like I have just done, it sounds very similar to the enum/stack/loop form that we thought we didn’t have! Implicitly, of course, we still have all of these items, but it is striking to notice how we can express the computation in very different constructs.

Wrapping up

Most of what was in this chapter is ‘information only’. That is, it’s really useful to know, but there are no real ‘wow’ moments.

The example at the end of the chapter was somewhat more involved than I was expecting, and took me a few minutes to figure out what was going on. Once you see it, the code looks terse and elegant. Until that point, however, it feels slightly impenetrable to the outsider.

Next time, the F# interactive tool!

No comments:

Post a Comment