Imperative computation in F# (I.) - Returning results from a function
One of the limitations of F# is that it doesn't very well support some of the
advanced imperative language constructs such as break
, continue
or imperative style of returning value from a function, meaning that you can't write
something like return false
in the middle of the function. This has
good reasons. F# doesn't in principle have the notion of currently executing statement
and instead treat every code you write as an expression. Clearly, when there is no
current statement, we cannot jump to other statements. If you're looking
for more information about these basic principles, you can take a look at my book
Real World Functional
Programming, which covers this distinction in details in chapter 2, but we'll look
at a brief example that will clarify this idea shortly.
Often, there is really no need to use break
or other imperative constructs
in F#, because you can write the same thing more elegantly using one of the provided higher
order function such as Seq.exists
or Seq.tryfind
. However,
there are still some cases where the imperative programming style makes it easier
to express our original intention. Also, implementing your own higher order
functions (akin to Seq.exists
) would sometimes be much easier if we
could just use imperative return.
So, what can be done about this? Adding imperative language features to F# seems
a bit weird (at least to me), because it breaks the nice notion that everything
is an expression, which makes it easy to reason about what the code does (again,
there is an example of this in chapter 2 of my book). On the other hand, having these
features would be really nice sometimes. In this article series, we'll look how to
implement computation expression that allows us to write imperative code in F#.
Computation expressions (an introduction is in chapter 12) allow us to create blocks
of F# code that add some non-standard meaning to the usual F# code. In this first
article, we'll allow returning from anywhere inside the body using
return <expr>
. In the next articles of the series, we'll
look at more imperative constructs such as break
and continue
and we'll look even further and explore some features that aren't available in
C#, but can be useful sometimes.
Programming with expressions
When programming in F#, everything is an expression. The easy way to understand
what this means for a C# programmer is to imagine that we'd have to use conditional
expressions (<expr>?<expr>:<expr>
) instead of the usual
if
-then
-else
construct. Other constructs such
as for
loop would be also slightly different, but we don't need to go
into the details. Now, C# allows you to use return
as a statement
anywhere, but you cannot use it as an expression, which means that the
following isn't valid C# code:
return
(input == 1)
? (20 + 22)
: (4 * (return false));
The purpose of this example was just to demonstrate that disallowing imperative
return
in F# makes quite a good sense. The next point that I want to make
before looking how to get imperative programming style in F# is that in many
situations you don't really need to write the code in the imperative way.
For example, let's say that you want to return true
when some input
collection of integers contains a number larger than 10 and false
otherwise.
The usual way to write this in C# would be something like this:
foreach(var n in input)
if (n > 10) return true;
return false;
There are various ways to write the same thing in F# - if you wanted to write this
for the IEnumerable<T>
type directly in F#, you couldn't use
built-in for
loop (because there is no way to return from it). This means
that you'd have to call GetEnumerator
explicitly and write a recursive
function to do the processing. That sounds like a lot of pain. However, the F#
library contains many generally useful higher order functions that make problems
like this trivial. The previous example can be implemented in F# like this:
Seq.exists (fun n -> n > 10) input
As you can see, this is even easier than the original imperative solution.
In fact, when using C# 3.0 and LINQ you would probably use one of the extension
methods together with C# lambda expressions to write the code in a functional
way even in C#. This means that before you'll think about using imperative style
in F#, you should always check whether the same thing cannot be done in a lot
simpler way using functions like Seq.fold
, Seq.exists
or Seq.tryfind
. That said, we can now look how to simulate
imperative programming in F# when we're really sure that we want it.
Imperative computations
When implementing F# computation expression, we need to create a builder
object that contains a few primitive members that are used to compose the code
from parts of the computations written by the user. We'll look at the implementation
of the builder in a second. Once we'll have it - we'll call it ImperativeBuilder
,
we can write the following code to create imperative
value that's used when
writing all the imperative computations.
let imperative = new ImperativeBuilder()
This one line of code created the builder, which we can now use to write imperative
style of code in F#. We'll start by looking at some trivial example to demonstrate
how the code works under the cover. Then we'll look at the implementation of the
ImperativeBuilder
and finally we'll write some more real world example.
The following listing shows a single imperative function that contains the return
statement two times. The behavior should be the same as C# - we want the first value
to be returned and we don't want the printfn
function to be ever called.
let test() = imperative {
return 0
printfn "after return!"
return 1 }
When the F# compiler sees this code, it translates it into a couple of calls to
primitives provided by the imperative
builder. The most important primitives
for us now are Return
, which is used when translating the return
keyword and Combine
. The return
keyword denotes the end of a
sub-expression, so when we use it multiple times, the compiler needs some way to combine
these sub-expressions into one and that's exactly when it uses the Combine
primitive.
imperative.Run
(imperative.Combine
(imperative.Return(0),
imperative.Delay(fun () ->
printfn "after return"
imperative.Return(1))))
There are a few other primitives that you can see in this example. We'll talk about them
shortly when discussing the implementation. However, the important point that the original
code that contained multiple return
constructs (and felt like a sequence of
statements) has been translated into a standard F# expression.
Designing the computation type
The key question when implementing computations like this one is what the type
of the expression we've seen above is? We'll represent the computation using a type called
Imperative<'T>
and most of the primitives above work with this type.
In particular, the Return
primitive takes a value of some type - in our
example above int
and returns a value of type Imperative<int>
.
The Combine
primitive takes two values of type Imperative<int>
as an argument and returns a combined value of the same type.
Now, what should this type look like, so that we can implement the desired imperative
behavior using the computation builder primitives? First of all, it must be a lazy type,
which means that the printfn
function shouldn't be called when evaluating
the arguments for the Combine
member. The easiest way to achieve this is
to write the type as a function that takes a unit
as an argument and returns
something. This means that we don't have to run the second part of the code in the
Combine
primitive, when the first part already returned some value.
The second question is - what should be the value returned by this function? We must be
able to represent computations that don't return any value (for example, when we have
if
-then
loop without the else
clause and the condition
evaluates to false
) and we need to represent computations that
return something using the return
primitive. The usual way to
represent a value that may be missing in F# is to use the option<'T>
type, so we'll end up with a type that looks like this:
type Imperative<'T> = unit -> option<'T>
This type can easily represent the two parts of the computation we've seen above.
The first one (containing only return
) will be represented as a function
that immediately returns a value Some(0)
and the second one will be
represented as a function that prints string using the printfn
function
and then returns a value Some(1)
.
A more complicated computation such as the if
-then
conditional without the else
clause would be represented as a function
that evaluates the condition; if the condition is true
it runs the computation in the
then
clause and returns its result and otherwise returns None
.
Implementing the computation
Now that we have the type that can represent the computations let's have a look
at the implementation of the computation builder. We'll start by implementing the
core primitives that were used in the example above. In the last section
of the article, we'll also add support for two looping constructs that can be
used inside computation expressions (for
and while
loops).
type ImperativeBuilder() =
// Creatae computation that returns the given value
member x.Return(v) : Imperative<_> =
(fun () -> Some(v))
// Create computation that doesn't return any value
member x.Zero() = (fun () -> None)
// Return a computation that will evaluate the provided function
// only when the computation is being evaluated
member x.Delay(f:unit -> Imperative<_>) =
(fun () -> f()())
// Combines two delayed computations (that may return
// value imperatively using 'return') into one
member x.Combine(a, b) = (fun () ->
// run the first part of the computation
match a() with
// if it returned, we can return the result immediately
| Some(v) -> Some(v)
// otherwise, we need to run the second part
| _ -> b() )
// Execute the imperative computation
// expression given as an argument
member x.Run(imp) =
// run the computation and return the result or
// fail when the computation didn't return anything
match imp() with
| Some(v) -> v
| None -> failwith "nothing returned!"
The first two members give us a way to construct primitive computations. The
computation created using Return
is used when you write return
and represents a computation that returns something when executed. The second one
represents a computation that doesn't do anything and is used in places where
the F# compiler needs to fill in some blank space (for example when we don't
provide the else
clause). The Delay
is also interesting.
As we've seen when looking at the translation, it is used to wrap the second
part of the computation when using Combine
to make sure that the
code which can do some side-effects (such as printing to the console) will be
executed only when the second part of the computation is actually needed.
The first three primitives are used for creating individual parts of the computation
that are later composed together into a single computation that represents the
whole imperative block of code. This composition is done using the Combine
primitive, which will get two computations as an argument. It returns a function
(delayed computation) that, when executed, tries to run the first computation
and returns its result if it returns a value. If the first computation doesn't
return, it simply runs the second one and returns its result.
Finally, the computation builder also contains the Run
primitive,
which is used to wrap the whole computation. Inside this primitive, we run the
computation to convert the internal representation (Imperative<'T>
)
into the actual value that we want to return ('T
). When the
computation returns None
, we throw an exception because this means
that the computation completed without ending with any return
statement
(we could also use Unchecked.defaultof<'T>
to return
zero or null
if we wanted to simulate the behavior of C/C++,
but we don't want to go that far in this article!) .
Real world example
Now that we've implemented the computation builder, we can take a look at some more realistic example where you may want to use imperative coding style. Most of the really interesting use cases will need imperative loops, so we'll get back to them soon, but even without loops, we can already write quite interesting code. The following listing shows a function that validates whether a provided string is a well-formed name. It uses various heuristics such as that the name should be at least two words, both reasonably long and both starting with an uppercase letter (By the way, sorry if your name doesn't pass the test - it's just a demo!)
> let validateName(arg:string) = imperative {
// Should be non-empty and should contain space
if (arg = null) then return false
let idx = arg.IndexOf(" ")
if (idx = -1) then return false
// Verify the name and the surname
let name = arg.Substring(0, idx)
let surname = arg.Substring(idx + 1, arg.Length - idx - 1)
if (surname.Length < 1 || name.Length < 1) then return false
if (Char.IsLower(surname.[0]) || Char.IsLower(name.[0])) then return false
// Looks like we've got a valid name!
return true }
val validateName : string -> bool
> validateName(null);;
val it : bool = false
> validateName("Tomas");;
val it : bool = false
> validateName("Tomas Petricek");;
val it : bool = true
The code uses quite common imperative programming pattern. It implements
various checks that are applied to the name and when the check fails, it
immediately returns false
as the result, otherwise it continues
to the next check. In functional languages, this can be implemented either
using nested if
-then
-else
conditions
or using pattern matching. If we wanted to avoid nesting in the second case,
we'd have to move the entire check into the when
clause, which
would complicate the code. This means, that the code above is quite elegant
solution for the problem and is in many ways cleaner than code you'd write
using standard functional constructs.
In this example the difference between imperative and functional version
would be mostly syntactical. I believe that syntax matters, so this is
a good argument for me, but it may not be convincing example for everyone.
In the next section, we'll add support for using imperative return
inside loops, which will simplify solving some problems in a more significant way.
Supporting imperative loops
If we want to allow the user to write for
and while
loops
inside computation expressions, we need to add two additional members to our computation
builder. In most of the cases, these members can be implemented in terms of other
primitives (such as Combine
and Zero
) by using the code
you can see below. Note that exactly the same code would work for many other computation
expressions, because we don't use the Imperative<'T>
value directly.
However, in the next articles of this series, we'll need to change this implementation
a bit, so it is good that the F# language allows us to implement this ourselves.
type ImperativeBuilder with
member x.For(inp:seq<_>, f) =
// Process next element from the sequence
let rec loop(en:IEnumerator<_>) =
// If ther are no more elements, return empty computation
if not(en.MoveNext()) then x.Zero() else
// Otherwise call body and combine it with a
// computation that continues looping
x.Combine(f(en.Current), x.Delay(fun () -> loop(en)))
// Start enumerating from the first element
loop(inp.GetEnumerator())
member x.While(gd, body) =
// Perform one step of the 'looping'
let rec loop() =
// If the condition is false, return empty computation
if not(gd()) then x.Zero() else
// Otherwise, call body and then loop again
x.Combine(body, x.Delay(fun () -> loop()))
loop()
We use type augmentation to add the two members to the existing
ImperativeBuilder
type. The implementation of both of the new primitives is
quite similar. In both of the cases, we create a recursive function that performs
one step of the looping and returns computation that should be executed after the
step is performed. This means that the loop is 'unfolded' into a series of
computation steps that are combined using the Combine
primitive
that we declared above. Note that we're also using the Delay
primitive
to make sure that the next step of the looping is run only when it is actually needed.
As discussed earlier, the Combine
member may not need to run the
second argument when the computation given as the first argument already returns some
value (using return
). For our looping primitives, this means that
whenever one step of the looping runs the Return
primitive, we'll stop
looping and immediately return the result.
Returning from while loop
Now, let's have a look at how can we use the looping constructs to solve some
real world problems. We'll start with the while
loop and we'll use it
to create a loop that reads names from the console and returns the first one that's valid:
> let readFirstName() = imperative {
// Loop until the user enters valid name
while true do
let name = Console.ReadLine()
// If the name is valid, we return it, otherwise
// we continue looping...
if (validateName(name)) then return name
printfn "That's not a valid name! Try again..." }
val it : (unit -> string) = <fun:clo@@0>
> readFirstName();;
Tomas
That's not a valid name! Try again...
Tomas Petricek
val it : string = "Tomas Petricek"
The listing starts by implementing the readFirstName
function. If we look at
its signature printed by the F# interactive, we can see that it returns string
,
which is exactly what we wanted. The function contains an infinite loop written using
while true do
loop, but because we can imperatively return from loops using
return
, it isn't actually an infinite loop. The function starts by
reading a name from the console and then uses the function we created earlier to test
whether the name is a valid name. If it is valid, then we want to return it as the result
and if it is not, then we continue looping.
The interesting thing is, how the F# compiler translates the if
expression.
In the true
case, it uses calls something like imperative.Return(name)
to create an imperative computation that returns the name. We didn't provide any code to
use for the false
case, so the compiler uses the "empty computation" we provided
and generates something like imperative.Zero()
. As we've already seen, the
Zero
member returns a computation that doesn't yet have the return value.
This means that when we finish executing the body of the loop and get back to the Combine
primitive, it will execute the next iteration of the loop. On the other hand, if we return
value using Return
, the Combine
primitive will see that we
already have the return value, so it will stop looping.
Returning from for loop
To finish our discussion, let's look at one more real-world example where the imperative
return
is useful. In the beginning of the article, I wrote that we don't
need to use imperative constructs in many of the cases, because the F# library contains
higher order functions that make it possible to express the same thing (often even more
concisely). However, implementing these higher order functions (such as Seq.exists
)
isn't as easy as it could be. For example, the exists
function that takes
a sequence as an argument has to be implemented by directly using GetEnumerator
.
The following example shows that implementing exists
function taking
a sequence and a predicate as arguments using our imperative
computation
is very easy, because we can simply use for
loop to enumerate over the source
sequence. When the predicate returns true
for the first time,
we can immediately terminate the execution and return true
as the overall result.
> let exists f inp = imperative {
for v in inp do
if f(v) then return true
return false }
val exists : ('a -> bool) -> seq<'a> -> bool
> [ 1 .. 10 ] |> exists (fun v -> v % 3 = 0)
val it : bool = true
As you can see, the implementation of the function couldn't be easier. If the
predicate returns true for any of the elements, we imperatively return true
and if all the elements are enumerated without finding any element like that,
we come to the last line of the function, which returns false
. It is
important to note that in the example above, the predicate is executed only
for elements 1
, 2
and 3
, because when we
reach 3
, we can return true
without accessing the rest of the list.
Summary
In this article, we implemented the first version of the imperative
computation
builder that allows us to use imperative return
statement that returns the result
from any place in the function. We started with a relatively simple version and then we extended
it to also support two standard F# looping constructs - for
and while
.
We also looked at various examples that demonstrate situations where the imperative
programming style allows us to write some code in easier way than when implementing the
same functionality in the purely functional way.
However, there are more imperative constructs that you may sometimes need than just
imperative return
. In particular, many of the F# users sometimes ask for
break
and continue
. Indeed, both of these can be added to our
computation builder. We'll need to do a minor change to the way we represent the computation,
but as you'll see in the next article of this series, it is definitely possible to do that
and it can be done in a very syntactically convenient way.
Downloads & References
Published: Thursday, 19 March 2009, 2:05 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: functional, academic, f#