Adventures in F# - F# 101 Part 9 (Control Flow)
Where We Are
Before we begin today, let's catch up to where we are today:
- Part 1 - Basic functional programming
- Part 2 - Currying and Tuples
- Part 3 - Scope, Recursion and Anonymous Functions
- Part 4 - History of F#, Operators and Lists
- Part 5 - Pattern Matching
- Part 6 - Lazy Evaluation
- Part 7 - Creating Types
- Part 8 - Mutables and Reference Cells
A Survey of .NET Languages And Paradigms
Joel Pobar just contributed an article to the latest MSDN Magazine (May 2008) called "Alphabet Soup: A Survey of .NET Languages And Paradigms". This article introduces not only the different languages that are supported in the .NET space, but the actual paradigms that they operate in. For example, you have C#, VB.NET, C++, F# and others in the static languages space and IronRuby, IronPython among others in the dynamic space. But what's more interesting is the way that each one tackles a particular problem. The article covers a little bit about functional programming and its uses as well as dynamic languages. Of course the mention is made that C# and VB.NET are slowly adopting more functional programming aspects over time. One thing I've lamented is the fact that VB.NET and C# are too similar for my tastes so I'm hoping for more true differentiation come the next spin. Instead, VB would be really interesting as a more dynamic language and not just one that many people just look down their noses at. Ok, enough of the sidetracking and let's get back to the subject at hand.
Control Flow
Since F# is a general purpose language in the .NET space, it supports all imperative ways of approaching problems. This of course includes control flow. F# takes a different approach than most functional programming languages in that the evaluation of a statement can happen in any order. Instead, in F#, we have a very succinct way of doing it in F# with the if, elif, else statements. Below is a quick example of that:
#light
let IsInBounds (x:int) (y:int) =
if x < 0 then false
elif x > 50 then false
elif y < 0 then false
elif y > 50 then false
else true
What I was able to do is to check the bounds of the given integer inputs. Pretty simple example. As opposed to many imperative languages, when you are returning a value from the if, all subsequent elif or elses must also return values. This makes for balanced equations. Also, if you return a value from an if, then you are also forced to have an else which returns a value.
Although F# is using type inference to determine what my IsInBounds method returns, I cannot go ahead and return one type in an if and another different type in the elif or else. F# will complain violently, as it should because that's really not a good design of a function. Below is some code that will definitely throw an error.
#light
let IsInBounds (x:int) (y:int) =
if x < 0 then "Foo"
elif x > 50 then false
elif y < 0 then false
elif y > 50 then false
else true
As I said before, the equations must be balanced. But of course if your if expression returns a unit (void type for those imperative folks), then you aren't forced to have and else statement. Pretty self explanatory there.
Let's move onto the for loops. The standard for loop is to start at a particular index value, check for the terminate condition and then increment or decrement the index. F# supports this of course in a pretty standard way, but by default, the index is incremented by 1. You must note though that the body of the for loop is a unit type (void once again) so, if you return a value, F# won't like it. Below is a simple for loop to iterate through all lowercase letters.
#light
let chars = [|'a'..'z'|]
let PrintChars (c:array<char>) =
for index = 0 to chars.Length - 1 do
print_any c.[index]
PrintChars chars
But, if I tried to return c from the for loop, F# will complain, but it will allow it to happen. It's just a friendly reminder that it's not going to do anything with that value you specified. I could also specify the for loop with a decrementer, so let's reverse our letters this time.
#light
let chars = [|'a'..'z'|]
let PrintChars (c:array<char>) =
for index = chars.Length - 1 downto 0 do
print_any c.[index]
PrintChars chars
F# also supports the while construct as well. This of course is the exact same as any imperative construct, but with the caveat of once again, the while loop should not return a value because it is of the unit type.
#light
let chars = ref ['a'..'z']
while (List.nonempty !chars) do
print_any (List.hd !chars)
chars := List.tl !chars
This time we're just printing out a char and then removing it from the list collection. Note that we're using the ref keyword and reference cells as we talked about before. Lastly, let's cover one last construct, the foreach statement. This is much like we have in most other languages, just the wording is a bit different. As always, the foreach statement has the unit type, so returning values is a warning.
#light
let nums = [0..99]
for n in nums do
print_any n
Wrapping It Up
Just a quick walkthrough of just some of the imperative control statements allowed by F#. As you can see, it's not a huge leap here from one language to the next. I have a couple of upcoming talks on F#, so if you're in the Northern VA area on May 17th, come check it out at the NoVA Code Camp.