F# Beginner Function Declaration Gotcha
In this example F# I’m attempting to declare a simple function that just prints “hello”, and then call it three times. What do you think the following code will print?
let printHello = printfn "Hello"
printHello
printHello
printHello
Well the code compiles, but when it runs it just prints “Hello” once. And that’s because printHello
is not actually a function, it’s a value of type “unit
”. We can tell this by hovering over printHello
and seeing the intellisense saying “val printHello : unit
”
So in the let
statement, we call printfn
there and then, and assign it’s return value (unit
) to printHello
. And the three “calls” to printHello
are not function calls at all. The F# compiler has no problem with these three statements which are effectively no-ops.
So how should we write this function? Well we need to use parentheses to indicate that printHello
is actually a function with no parameters, which is the same as saying it’s a function that takes “unit
”. That looks like this:
let printHello() = printfn "Hello"
Now if we hover over printHello
we see that it is a function that takes unit
and returns unit
: “val printHello : unit -> unit
”
With this correct definition, we can now call our function three times and get the expected text printed three times. We again need to use parentheses to indicate we are passing unit
into this function:
printHello ()
printHello ()
printHello ()
If we forget the parentheses, and just say printHello
on its own, this time we’ll get a compiler warning, telling us “This expression is a function value, i.e. is missing arguments”.
Anyway, hope this helps someone. I managed to make this mistake a few times recently and it took me a long time to spot what I’d done wrong. The moral of the story is to pay attention to the intellisense hints the compiler is giving us.
By the way, Visual Studio Code with the Ionide plugin has a really nice way of visualising the type of each let
statement, which makes it even easier to spot this mistake:
Comments
Yup, I fell for something similar very recently. I was writing a non-total function and passing in a
Yawar Aminfailwith "Error"
argument to throw an exception in the error case, except the exception was always getting thrown. I finally realised it was being eagerly evaluated before my function was ever called. D'oh moment.