In the previous post we looked at assigning values and the different types that those values could be. In this second installment we will be looking at functional programmings namesake: functions.
Functional programming as a paradigm is quite a hard thing to pin down, just like other paradigms. In object-oriented programming the one thing that really isn't up for debate though is that the general idea is that we have an object (whatever that may mean to you) and we represent data and behavior in these objects. In functional programming then it will come as no surprise that functions are first class citizens and that we accomplish our goals by transforming data using these functions.
What is a function though?
A brief reminder about mathematics
Do not worry, I will not be going into deep mathematics theory here. I instead want to remind you of some mathematics you probably touched on in school just to show you that this isn't necessarily something completely new to you. Secondly, it will show that functional programming has roots that go far deeper than computer programming. Do not worry though if you didn't like this stuff at school, I promise this is way more
In mathematical terms a function is a process that associates each element x of a set X to another value y which is of set Y. Let us call this process f. Then we have an expression:
y = f(x)
This is usually read as "let y equal f of x".
The set X of possible values of x is known as the domain. The set Y of possible outputs y is known as the codomain. To label the parts of the expression, x is the argument and the value of the function is the output.
So how would we accurately define a specific function?
let f: R → R be the function defined by the equation f(x) = x2, valid for all real values of x
Notice how we have the
Domain -> Codomain defined using
->. We will come back to this a little later.
One last thing. Remember common functions like
cos? It is common to write them as
cos x, without the the brackets as long as this does not lead to any ambiguity in the meaning. So now that we have had a little mathematics refresher, let us see if this brings us any insight into F# functions.
Defining functions in F#
In the previous post we looked at assigning values. F# being a functional-first language means that we can treat functions like any other value.
// int -> int let f x = x*x let y = f 3 //val y : int = 9
So above we define function f that takes argument x. Then we pass
3 as the argument and assign that to value
Another way to define functions in F# is to use the
fun keyword. Let us define the same function again, this time as function g:
// int -> int let g = fun x -> x*x let z = g 3 //val z : int = 9
This way makes it way more explicit that
g is simply just another value that is assigned to. Note that this way of defining functions using
fun is common when using functions once off inline, say for filtering a collection. We will see this in more detail in a later post when we dive deeper into collections.
In the above code I put the signature of the function in a comment above it. The signature describes what types a function take in and what it returns. So for our function above we have
int -> int. This means our function takes a single
int as an argument and then returns an
A function always has an input and an output. In F# (and all programming languages I know of) a function can have multiple arguments. Say for example we had a
writeToFile function that took a
boolean for whether to overwrite the file if it exists, and a
string with the content of the file. The signature for
writeToFile would then be
bool -> string -> unit. Now what is this
unit type? It was mentioned in the previous post as the type that represents nothing. As already mentioned, functions must always have an input and an output, so if a function has no meaningful value to return, we return
Do you see a similarity here? Types define the possible values that are possible. So for a signature
int -> int, our domain is all possible numbers allowed by the type
int numbers and our codomain is also all possible
int values. Pretty cool right?
In the previous function definitions you may have noticed we defined no types but the F# compiler inferred that the type of x was
int. This is because we used the multiplication
(*) operator on it. Most of the time the compiler does a pretty good job of working out the type. This keeps our code clean from boilerplate cruft. To be sure though, if you are used to a language like Java or C#, this will take a bit of getting used to. My tip is to pay close attention to the signatures. Any IDE will display this all the time or at least on mouse over.
If you prefer to be explicit or in those cases where the compiler needs some help to determine the type, you can easily define the types explicitly.
let f (x:int) = x*x // define argument type let f x : int = x*x // define only return type let f (x:int) : int = x*x // define argument type and return type
The argument type is specified with
(x:int). The parenthesis are needed to disambiguate the argument type from the return type.
Just a quick style note. Mostly in F# code the types are left off unless needed.
I wanted to highlight another way of defining functions, and that is by defining a type signature.
type Unary: int -> int let increment : Unary = fun x = x + 1 let decrement : Unary = fun x = x - 1
A unary function is one that takes only one argument
So we define a Unary function as one that take in a single number and returns a number, and then we have multiple implementations of that type.
In this post we introduced some of the core ideas behind functions. We learned how to define them and how to read a functions signature. We also touched on what the compiler can do for you by inferring the types, and how you can be explicit about the types.
In the next post we will dive deeper into Working with Functions and getting them to work for you.