Control Flow Constructs in F#
In F# you have several constructs available to you for controlling the flow of
your application. Some of these will be familiar to imperative programmers and
others may seem a bit foreign. We’ll start with an imperative programmer’s best
friend, the if statement.
let getBigNumbers x =
if x > 1000 then
“Its big”
else
“Eh, Its not so big”
No big surprises here. We’ve got an
if/then/else construct that returns a couple of strings depending on how our
conditional Boolean expression evaluates. Now if we wanted to provide an if else
block, we would use the elif/then construct.
In if/else statements you often find yourself using multiple comparison and
conditional operators. Here are some of the operators available to you:
Comparison operators: >, <, >=, <=, =, <>
Conditional operators: &&, ||
I should note here that the equals operator is a single equals sign (=), not the
double equals sign (==) that some of us are used to. Also, the not equals (<>)
is a bit non-standard (at least for those of us coming from C derived
languages).
Ok, so I lied, there is one very big difference between if statements in F# and
imperative languages; In F# all if statements must return a value. In fact, you
could say that the if/else construct in F# IS a value. And because it must
return a value it also must have an else block to insure that a value is
returned. To give you a better idea of what’s going on, here are a few examples
that are INVALID:
let a = if x > 1000 then “Its big”
elif x > 0 then “Well its bigger than zero!”
let b = if x > 1000 then 1 else “test”
The first if/elif does not contain an else block therefore it’s invalid. Again
just to hammer the point home, because if/else is a value itself we must ensure
that it evaluates to a value. The only way to prove to the compiler that it will
evaluate to a value is to provide the else block.
In the second if/else above we return two different data types, an integer and a
string. Obviously in a strongly typed language like F# this will not do. Your
if, elif and else blocks must all evaluate to the same data type.
Pattern Matching
I covered pattern matching in another article but we should definitely mention it here for completeness because it is an important piece of F#’s flow control. You can think of pattern matching as a much more powerful switch statement. Here are some of the coolest aspects of pattern matching in my opinion:
- Your patterns can decompose data structures like tuples and lists and match against specific values within those structures.
- You can pattern match against the type of an object, which is something you can’t do in C#. For instance, if I had an object of type IEnumerable and I wanted to see whether it was a List or a DataView or whatever, you could match to those specific types and run code based on the type of the object. You can do this in C# but you have to write lots of code (think of the is operator or using the as operator and checking for null).
- You can beef-up your patterns with guarding rules (basically Boolean conditionals) and by combining multiple patterns into one.
You can see examples of pattern matching here
Abstracting Flow Control
Abstraction of flow control is exactly what it sounds like, abstracting away the implementation details of flow control and allowing the consumer (that’s us) to focus on the problem at hand. This is a common feature in functional languages and is now beginning to find its way into languages like C# (for instance the ForEach method on the generic List collection).
Ok, probably doesn’t mean much to you yet so lets come up with a problem. Lets
say we have a list of integers 0-100 and we want to print those integers to the
screen if they are divisible by 3 (Boy, what an exciting problem!) Here’s how an
imperative programmer might do this in C#:
List mylist = new List();
for (int i = 0; i < 100; ++i)
{
mylist.Add(i);
}
foreach (int j in mylist)
{
if (j % 3 == 0)
{
Console.WriteLine(j);
}
}
Now surely we’ve all written code that looks like this. But from a language design perspective what might be wrong with this? Well, lets think about the for loops. As programmers we’re always iterating through a collection and doing something. We’re programmers, that’s what we do! So why require us to write the same code over and over again?
By abstracting the flow control structures we can focus only on the bits that make our problem unique. However, to do this our language has to allow us the freedom to pass around chunks of code (or functions) easily. That’s what makes functional programming so powerful. In F# we can solve the same problem like this:
List.iter (fun x -> if x % 3 = 0 then printfn “%d” x) [1..100]
Boy is that terse! Here we are making use of the List library’s iter function which will iterate through a list and perform some function on each element in the list. The List library has several methods like iter that abstract flow control and take functions and lists as arguments.
As you can see, abstraction of flow control is a powerful technique and is one of the major benefits to a functional language. Well, I guess that’s enough flow control for today. Happy programming!