When new to F# its not uncommon to find your self struggling to understand why your nice new shiny piece of code won’t compile. With F#’s amazing ability to infer function parameter and return types, a type mismatch situation can be projected through quite a bit of code. This projection of an issue can make it a bit tricky understanding what’s going on and understanding signatures is big help to solving this.
The F# Interactive in Visual Studio is a good place to experiment with values, type and functions to see what signature the compiler applies to them. Also, hovering over a function or value with the mouse pointer will reveal the signature in a tool-tip.
Value binding signatures
Lets start with some simple value binding signatures.
When reading a value signature, start with the right most keyword and then work backwards.
example02 reads as a list of integers and
example05 reads an option of list of a generic type. When you see an option of some specific thing, like a list. Then it can only be
Some( list ). And when you have a list of a generic type it can only be . Therefor
example03 is similar because when you come across option of a generic type, it can only be
None. You see this pattern repeat again with
example09 looks a bit more tricky, but again reading from right to left reveals it to be an option of list of option string. E.g.
When a type cannot specifically inferred then the signature notation
a' is used. This is a generic type. If a second type cannot be inferred then it will be
b' and so on.
It is worth mentioning that in function decelerations when a type cannot be inferred then the notion is
Here are the actual value bindings to the signatures above.
Value binding reconstruction
If it helps, you can even try to reconstruct the value binding statement from the signature. Consider the signature
val xtr : (float * string list) list, start by replacing the
=. Next, working from the right to the left, remove the list and enter square brackets around rest. So far we have this:
let xtr = [(float * string list)] The next item is the round bracket (which is always paired), when they contain items that are separated with
* then this indicates that this is a tuple. The tuple round brackets can remain untouched. Moving on, remove the list statement and enter square brackets around the item before. The scope here is to the tuple’s sub item. The result so far should look like this:
let xtr = [(float * [string] )] Moving on, then next item is a string, replace this with a literal string example, or several if you want because this is in a list. Next, the
* of the tuple can be replaced with a comma and the float can also be replaced with a literal float value. The final result could be:
let xtr = [(3.14 , ["red"] )] // val xtr : (float * string list) list
Function binding signatures
A function binding signature is identified because it starts and ends with round parenthesis and contains one or more ‘maps to’ operators (
When decomposing function signatures, it is best to process them from left to right. Unlike value signatures where the opposite is best.
Either side of the ‘maps to’ operators are value signatures, the last value signature represents the result. All the others are input value signatures.
func01, this function maps a value of generic type to another value of the same type.
When a function has no return value, the signature will show the last type as
func03 is an example of a function with a
unit return type.
A more complex function is
func07. It is a function because of the round brackets and the ‘maps to’ operators. There are three ‘maps to’ operators, and therefore three input types and a return type. Two distinct types are used,
'a for the key and
'b for the value. In summary,
func07 is a function that takes a key and a value and a map of the same key value types, then returns a new map with the same key value types.
Following are the functions declarations to the signatures above.
About functions in general
A function in F# maps an input value to an output value.
Even though many of the examples above have multiple input parameters, under the covers F# processes functions with only one input and one output parameter. F# decomposes functions with multiple input parameters into multiple functions with one input and one output parameter.
func02 is processed by the F# compiler, the initial parameter x is taken and wrapped into a new function as a closure. The new function only takes only y as a parameter. The new function now only has one input value and one output value, so can be executed.
The process is called Currying and was invented by the mathematician Haskell Curry.
I talk about currying in a Currying (Partial Application)