Although my last post leaves me with a bunch of things that deserve further explanation and further spelunking through the F# type system, the first order of the day is to make your knowledge of F# Turing-Complete so that you can start writing simple programs. And to do that you need to know how to write loops, which leads us to immutability.
By default variables in F# are immutable. Once you declare them you can’t change their value. So while in C# it’s perfectly normal to write:
var x = 1;
...
x++;
in F# there is no equivalent construct.
(All right, all right, I’m lying, F# also supports mutable variables. But using them is considered dirty. F# programmers, when forced to use mutable variables for performance reasons probably end up in the shower frantically scrubbing themselves while sobbing “I feel so dirty, I feel so dirty.”)
In fact they don’t even call them variables. They call them values, to further emphasize the fact that they don’t change, and to differentiate themselves from unwashed masses that use programming languages that don’t have theoretical underpinnings in lambda calculus.
The fact that variables are immutable has two advantages: one, the programs tend to look a lot like mathematical expressions which theoretically makes them easier to reason about; and two, they are easer to parallelize because the type system guarantees that there are no side-effects.
But there’s a problem. Without the famous i++ you cannot write loops. No loops and you can’t write an arbitrary program.
Fortunately, F# supports recursion, and recursion is as good as loops. For example, to write a function that prints “Hello World” ten times in a row, you could first write the following helper function and use it instead of a loop:
let rec loop n f =
if n > 0 then
f ()
loop (n - 1) f
let helloWorld () = Console.WriteLine "Hello, World!"
let _ =
loop 10 helloWorld
ignore (Console.ReadLine ())
The first function, loop, is dead simple. It takes a number n and a function f (void –> void, or unit –> unit in F# parlance), and if n is greater than zero calls f and then calls itself recursively with (n – 1) and f.
However, it introduces two new concepts. The first is that instead of writing:
let loop = something
we wrote:
let rec loop = something
The rec keyword tells the F# compiler that loop is a recursive function so that the compiler should be aware of the identifier loop when evaluating the function body.
Compilers in other languages don’t need a special keyword for recursive function declarations, so why the weird syntax? The keyword is there to prevent you from making unintentional errors. Remember, F# uses the let keyword for both function and variable definitions. And even in languages like C# saying:
var n = n + 1
is an error.
But since F# doesn’t really know if you are defining a function or a variable, it plays it safe and forces you to declare that a function is recursive.
The second concept we just introduced is the if-then expression. F# basically has three forms of the if statement:
1. if <condition> then <expression that evaluates to unit/void>
2. if <condition> then <expression1> else <expression2>. This behaves like the C# ?: operator, returning the value of expression1 if the condition is true and expression2 if it’s false.
3. if <condition1> then <expression1> elif <condition2> then <expression2> … else <expressionN>. This is just a more general form of if-else for cascading ifs.
The next function we have written is helloWorld. The only thing weird about it is the () after its name. Why do we need that? Why not simply write:
let helloWorld = Console.WriteLine "Hello, World!"
without the parenthesis at the end? We certainly didn’t have to use any parentheses when declaring the add function at the start of this tutorial.
Well, this again brings us to the fact that F# uses the let keyword for both functions and values. And since void/unit is a valid variable type in F#, in this particular case if we omit the parentheses the compiler would interpret it as roughly the following C# code:
void helloWorld = Console.WriteLine (“Hello, World!”);
In C# this immediately looks weird because void is not a valid type for variables, but if you construct a different example:
let readLine = Console.ReadLine ()
vs.
let readLine () = Console.ReadLine ()
it becomes apparent that the first case is a direct call to Console.ReadLine with the result being stored in the variable readLine, while the second case is the readLine function which, when called, returns the string that the user has entered.
The reason why I’m beating this dead horse so much is because void/unit is weird that way. Usually when you have a function that takes parameters of a different type the compiler can differentiate between a function call and a delegate reference, but for unit you have to force it.
Finally, our main function is simple. It calls the function loop with the number 10 and a delegate to the helloWorld function, and then simply waits for you to press Enter in order to see the result.
At this point you should know enough to start experimenting. You know currying, you know how to write functions, you know how to declare variables, you know how to do ifs and loops, and you know how to call into static members of the .NET Framework classes.
For example, say we want to replace the helloWorld function with a more general function to which you can pass the string to print out. Currying to the rescue:
open System
let rec loop n f =
if n > 0 then
f ()
loop (n - 1) f
let print (text : string) () = Console.WriteLine text
let _ =
loop 10 (print "Goodbye cruel world.")
ignore (Console.ReadLine ())
As you can see, we renamed the function helloWorld to print, and added an extra parameter text, for which we unfortunately had to specify the type because Console.WriteLine is overloaded for numerous types so we needed to tell the compiler that the actual type is. Then, in the main function, we combined the delegate print with the string “Goodbye cruel world", thus creating a function of type unit –> unit which we then sent into the function loop.
You now know enough to be dangerous. If you want to learn F#, now is the time to open up Visual Studio and start modifying the code. Maybe write a function that you can use as a for loop. Or modify the above loop function to pass the loop counter into the function f. Or write a factorial function. It’s going to be painful because both the syntax and the concepts are different than C#, but bang your head against the wall, consult Google, write a question in the comments, do anything to write these little programs because they will teach you more about F# than hundreds of books.
Part 4