Wikipedia says that
…functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions…
The point I wish to make here is that functions are the most important “things” in a functional programming language. You can say that functions are “first class citizens” in a functional programming language. This means that functions can be passed around as parameters and return values of other functions, just like any other data. Functions which can take other functions as parameters and/or return values are called Higher-Order Functions.
Functions and Type Inference
Here is an example of a simple function in F#.
let triple x = 3 * x
Here triple is the name of the function and x is the name of the parameter passed to the function. Contrary to C# naming convention for function, F# uses camel casing. Even though we have not specified any type information, the compiler is able to infer that this function takes an integer as parameter and returns an integer. This is called Type Inference and makes your F# program more concise.
How did I know that the compiler inferred the parameter and return value as int? I typed in the above functional declaration in F# interactive and terminated it with ;; and hit Enter. F# showed me:
val triple : int –> int
This means that triple is a function which takes an integer as parameter and returns an integer. If we try to call the function with a float as parameter
triple 3.0
it will spit out an error message as follows.
stdin(4,8): error FS0001: This expression was expected to have type
int
but here has type
float
We know that * (multiplication operator) works with floats as well, so why this error ? The reason for this is that in the absence of any additional type information the compiler simply defaulted the type to integer. So triple is a function which can take an integer as parameter and return an integer which is equal to three times its input. It is equivalent to the following C# function.
static int Triple(int x)
{
return 3 * x;
}
Since the function expects integer, a parameter of type float will produce an error.
So what do we do when we need a datatype other than the default type that the compiler is able to infer? We give a hint to the compiler via type annotation.
let triple (x: float) = 3.0f * x
Note: In the above example type annotation is not strictly required because the compiler would infer x as float because 3.0f is float. But it does show you how to annotate a function parameter with its type.
Higher Order Functions
The functions mentioned above are not a higher-order functions because they do not take a function as parameter, neither do they return one. List.map and List.filter are examples of higher order functions. List.map takes a function and applies it to each item in the list (also passed as parameter to it) and returns a new list with the output values. List.filter takes a function and list as parameters and returns a new list. The output list contains those values from the input list for which the input function returned true. An example is shown below which makes use of both these higher order functions.
let triple x =
3 * x
let isEven x =
x % 2 = 0
let actOnEven action numbers=
List.map action (List.filter isEven numbers)
[<EntryPoint>]
let main(args:string[]) =
let results = (actOnEven triple [1 .. 10])
Console.WriteLine (results)
0
Here actOnEven is itself a higher order function.
Recursive Functions
Recursive functions are functions that call themselves. An important tenet of functional programming is the use of recursion instead of looping. We can create recursive functions using the following syntax.
let rec factorial num =
if( num <= 1) then
1
else
num * factorial (num - 1)
The rec keyword is a helping hand to the compiler for type inference. Personally, I don’t like it but I think they couldn’t find a way around it.
Function Currying
Currying (named after Haskell Brooks Curry) is an interesting technique in which you pass one of the arguments to a function and it returns another function which takes the rest of the arguments. For eg., consider the function
let add x y = x + y
If you run this in F# interactive, you will see the following message:
val add : int -> int –> int
This means that add is a function which takes an integer and returns a function which takes another integer as parameter and returns an integer. So if we call add with just one parameter, it will return another function which takes the second parameter.
let addToTwo = (add 2)
To be clear, addToTwo is a function that takes one integer parameter and returns an integer.
For eg,
addToTwo 8
will return 10.
Here addToTwo is the curried function and this technique is called function currying. Although this may look like a strange and useless language feature, it has interesting and powerful applications in functional programming (like the pipe forward operator). I will write more about this in future blog posts.
Nested Functions
In F#, a function can contain another function. Here is a simple example.
let printList numbers=
let print num=
printfn "%i" num
List.map print numbers
Here print is a function nested in printList.
Some of the feature that I have mentioned here would be new to C# programmers and might feel a little strange. But have hope, for all will become more clear with time. We need to see some solid examples to appreciate the power of some of them. I have mostly presented useless examples but I hope a reader would have gleaned atleast a little information about functions and its usage in F# from this post. There is more to write but it is getting late (3 am) and tomorrow is a working day, so I gotta go. Will be back with more on F#.
References:
Programming F#
Expert F#