Posted in:

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:

image

Comments

Comment by Yawar Amin

Yup, I fell for something similar very recently. I was writing a non-total function and passing in a failwith "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.

Yawar Amin