Among newcomers to F#, there is so much confusion about the basic details of the language. The syntax look familiar, but actually much of what you see isn't at all what it looks like. That certainly is one cause of the confusion. I actually think this was done deliberately by the original language designers. (Not talking about Don Syme here, but somebody back in the Jurassic era or thereabouts.) Then there's what we're told, and there too things aren't what they say they are. That certainly is one more cause of the confusion. If nothing can be trusted, how are we supposed to understand?

I get a bit frustrated when I see one question after another on Stackoverflow that revolves around the same basic misunderstandings. I get a bit more frustrated when the answers frequently stops short of explaining the next problem the newcomer is about to hit.

So I thought I'd write an article that will help newcomers navigate this series of potholes in the road to F# heaven. These dark areas are all very closely related, so it makes sense to just explain all of it in one go.

I am fully aware that there are exceptions to pretty much all I explain. I was a top notch expert on the Borland Pascal and Delphi languages for a few decades, and knew well that you pretty much could not state anything at all about the language and platform without adding "except ...", which gets quite tiresome. So I don't. So do question everything I tell you here, and do expect plenty of mistakes and exceptions. The main thing is that you get a new perspective and understanding of what's going on.

Rules of thumb

These rules of thumb can help you understand the syntax and workings of the F# language. I am not sure I nailed it with these four, but it's worth a try.

  • Parentheses are only used to control order of evaluation in expressions.
  • When there's a comma, it's a tuple.
  • A function call with parameters use whitespace to separate the parameters.
  • A function always has one single argument.

Let's go through these rules again, because they are not well understood, even though they are very simple.

Tuples and parentheses

Parentheses are only used to control order of evaluation in expressions. They are not used to signify tuples. They are not used to signify argument lists. Commas are used to form tuples from individual expressions, but a parenthesis is only needed when the default order of evaluation needs to be changed. A parameter list with parenthesis and commas is not actually a parameter list - it is a tuple.

When there's a comma, it's a tuple. It is not a parameter list when used with a function. It is a tuple which is given as a single parameter to the function. Part of the confusion out there is due to the two conventions named curried form and tupled form. This is not technically rooted. (The word form can be exchanged with style or parameters or arguments.) Part of the confusion is due to F# mapping parameters to .NET functions through the use of tuples, which by the way is a brilliant way to do it. Part of the confusion is simply caused by everybody talking about parameters even when it's one tuple, one parameter.

Let's look closer at these expressions.

f1 (a, b, c)
f2 a, b, c

In the first expression, the function f1 is called with a single argument - a tuple with three elements. The parenthesis is there to control the order of evaluation, so that the tuple is formed by the three elements a, b and c, before being fed as one single argument to the function f.

In the second expression, there is no parenthesis. The rules of precedence then dictates that the function f2 is called with a as argument. The result of f2 a then becomes the first element of a tuple with three elements.

Perhaps this explanation isn't sufficient to convince everybody that the curried form versus the tupled form are not rooted in any language feature, but just a matter of conventions. To settle this once and for all, we can start by looking at this snippet.

let s1 = "aaabbb"
let s2 = s1.Insert (3, "xxx") // s2 becomes "aaaxxxbbb"

The Insert method is a .NET method with two parameters, which means in F# we pass a tuple with two elements. Thus, the above can also be expressed like this.

let s1 = "aaabbb"
let theParameters = 3, "xxx"
let s2 = s1.Insert theParameters // s2 becomes "aaaxxxbbb"

Now it suddenly became very obvious that the parameter for the Insert method really is a tuple.

We are of course discussing F# here, and not .NET methods. Behind the scenes the tuple is transformed by the compiler into parameters for the .NET method. No disagreement there either.

Functions and details

First, to clear up one of the most common beginner's mistakes - you can't have a function that doesn't have a parameter. The following code snippet is regularly seen in various incarnations on Stackoverflow.

let myTestFunction =
    printfn "Hello from test function."

let myMainFunction =
    printfn "Start."
    myTestFunction
    printfn "Stop."

The output of this is as follows.

Hello from test function.
Start.
Stop.

The problem is that the two functions are not functions, but values. When the program runs, values are evaluated from top to bottom. Printing of the strings is a side effect during the evaluations of the values. No functions are called here, and still the strings are printed.

To correct the situation, we need functions with parameters.

let myTestFunction () =
    printfn "Hello from test function."

let myMainFunction () =
    printfn "Start."
    myTestFunction ()
    printfn "Stop."

The functions don't have any need for the parameter values, so we just use an empty parenthesis when declaring, as well as when calling.

The empty parenthesis is not what it may look like to a newcomer to F#. It does not mean there are no parameters. It is not an empty parameter list. The empty parenthesis is actually a value symbol. That value is the only value that exists for the type named unit, which is a type that in some ways (but only some ways!) corresponds to the void concept in C#.

So the fact is, when we call a function using (), we are actually supplying that function with a parameter, which is a value as good as any other value. Have a look at this.

let printAnything x =
    match x with
    | () -> printfn "It's () alright! What else could it be?"

printAnything ()

let parameter = ()
printAnything parameter

let hello () = printfn "Hello, World!"
hello parameter

Convinced now?

The unit type is not really like void in C#. It is a type like any other type. Which leads us to the conclusion that we should be able to do the following also. Pretty useless, though.

let myFunction 007 =
    printfn "My name is James Bond."

myFunction 007 // prints "My name is James Bond."
myFunction 13 // MatchFailureException: "The match cases were incomplete"

It actually works, but you have to call it with the same value as was used in the declaration, or else get an exception. Since the unit type only has one value, that issue doesn't exist when () is used.

You can of course go completely crazy and do stuff like this.

let someCrazyFunctionOfMine (a, b) () 7 (1, 2, 3) "Hello" 13 = printfn "I'm ok."
someCrazyFunctionOfMine (2, 3) () 7 (1, 2, 3) "Hello" 13

I guess that would set fire to any discussion about tupled versus curried form.

Functions require parameters, but you don't necessarily see the parameters explicitly declared.

let add5 = (+) 5 // Takes one int argument.
let funcA = funcB >> funcC
let myPrintFunc = (fun s -> printfn "The string is %s." s)
let myPrintString = printfn "The string is %s."

It might be a good idea to - as a rule of thumb - stick to explicit parameters when there's a choice, in order to lessen the risk of confusion.

Currying

The third rule of thumb was that a function call with parameters use whitespace to separate the parameters. And the fourth rule of thumb was that a function always has one single parameter. Now, that seems like a serious contradiction. How can you separate the parameters if there is just one parameter? Is there just one parameter, or isn't there? This is where currying comes in.

type SecretAgent = { Index: int; FirstName: string; LastName: string }

let changeAgentName firstName lastName agent =
    { agent with FirstName = firstName; LastName = lastName }

let undercoverAgent = { Index = 007; FirstName = "John"; LastName = "Smith" }

let realAgent = changeAgentName "James" "Bond" undercoverAgent

The call to function changeAgentName supplies three parameters. But any function only takes one parameter. What's going on? We can imagine that the compiler rewrites the function to this. (Don't expect a real compiler to do this. Instead, imagine that you do it.)

let changeAgentName firstName =
    let f2 lastName =
        let f3 agent =
            { agent with FirstName = firstName; LastName = lastName }
        f3
    f2

There are three functions here - one function inside another - one function for each of the three possible parameters. Now it is a bit easier to understand what we mean by a function only taking a single parameter, and what the term curried really means.

And now we get to the really fun part too. You get to decide how many of the parameters you want to supply, counting from the left.

If you supply all three parameters, then you get a record in return. What happens is that changeAgentName take the first parameter, then call f2, which take the second parameter and then call f3, which take the third parameter and returns the modified record. This was the easiest case. See? No problem!

If you supply two parameters - the firstName and lastName - then what you get in return is a function. What happens is that changeAgentName takes firstName and calls2, which takes lastName, and ... you see, there is no argument for f3. What happens now then? We can imagine that the function f3 is returned with the firstName and lastName parameters filled in. The returned function will take the last parameter - agent - whenever you decide to call it.

We can use this mechanism to create a new function.

let changeToJamesBond = changeAgentName "James" "Bond"

let undercoverAgent = { Index = 007; FirstName = "John"; LastName = "Smith" }
let realAgent = changeToJamesBond undercoverAgent

We can now call changeToJamesBond with the last remaining parameter - the agent parameter.

There was one final case - when you supply only one parameter to changeAgentName. If you supply only the first parameter, you get a function in return - f2 with only firstName filled in - that can be used to change the LastName of a SecretAgent. The first name sticks to whatever parameter you supplied.

Note: Back to James Bond. When you create a function in the way demonstrated here with changeToJamesBond, be aware that it is actually a value which is calculated as part of the module or class initialization. As long as the code is purely functional, there should not be an issue. If however the code has side effects, such as accessing a database, then order of evaluation has to be considered carefully. Using the rewritten changeAgentName as example, the evaluation of changeToJamesBond during initialization will run changeAgentName and f2 during the initialization, but after that you can trust f3 not to be called until changeToJamesBond is called.

Finally, what is currying? Currying is the process of evaluation we have used here, where a series of functions each take a single argument until the arguments run out. What you are left with at that point is the final result.

Function signatures

Newcomers to F# tend to not go into depth on function signatures for quite a while. The point of those mysterious arrows and stars are lost. Now that we've looked at how currying works, the signatures are a bit easier to understand, and can even help us understand currying even better.

The signature of the changeAgentName function is like this.

firstName:string -> lastName:string -> agent:SecretAgent -> SecretAgent

There are many ways to look at this. The most obvious when you are not used to it, is that you have tree parameters and one result type. If you remind yourself that a function only has one parameter, then you can more easily understand that the signature can be interpreted like what follows here. We add one parentheses.

firstName:string -> (lastName:string -> agent:SecretAgent -> SecretAgent)

This tells us that changeAgentName takes one parameter, and returns a function with a signature like that of f2.

Let's have a look at f2's signature only, and also again use a parenthesis to make clear what is returned.

lastName:string -> (agent:SecretAgent -> SecretAgent)

f2 takes one parameter, and returns a function with a signature like that of f3, which is very simple.

agent:SecretAgent -> SecretAgent

f3 takes one parameter - a record of type SecretAgent - and returns a record of type SecretAgent.

So we supply parameters from the left side. There is a number of arguments, in our case three. If you don't supply all parameters, then what's left (I mean on the right side of the last parameter you supplied) is part of the result you get back. If what you get back contains an arrow in the signature, then it's a function. I think we can deduce from this that any time you don't supply all parameters, it means the result will be a function.

If you write a function that gets a function from somewhere else, and returns that, then implicitly you add more possible parameters to your function. There will be more arrows in your function signature. It sounds horribly complex when explained this way, but it's just everyday procedure in F#. The following is possibly not typical usage of this, but it is a simple example.

let add = // int -> int -> int
    (+)

let trick name = // string -> int -> int -> int
    printfn "%s" name
    add

let customAdd = // int -> int -> int
    trick "sum them"

let customAdd5 = // int -> int
    trick "add 5" 5

let result = trick "A" 2 3 // Will print "A", and then return 5.
let result2 = trick "A" 7 3 // Will print "A", and then return 10.
// The remaining calculations will not print anything as a side effect,
// since the printing has already been done once during initial evaluation
// of customAdd and customAdd5.
let result3 = customAdd 10 20 // Returns 30
let result4 = customAdd 100 200 // Returns 300
let result5 = customAdd5 1000 // Returns 1005
let result6 = customAdd5 2000 // Returns 2005

That's all so far. Hope this was of some help.

results matching ""

    No results matching ""