Road to Haskeller #1 - Functions, Types, Bindings

Last Edited: 6/12/2024

The blog post introduces functions, types, and bindings, which are all important concepts in Haskell.

Haskell & Functions

Functions

Let's start coding. Functions are an essential component of Haskell, and they can be defined as follows:

name arg1 arg2 ... argn = <expr>

where name is the name of the function, arg1 arg2 ... argn are the arguments/inputs of the function, and <expr> is the expression of the function. It is important to note here that return in Haskell is not the same as return in other programming languages like Python. When you use the defined function, you use it like the following:

name arg1 arg2 ... argn

Examples

Let's try working with examples to understand how functions work in Haskell. The following functions, double and inRange, double an input and check if a value is within a provided range, respectively.

double x = 2 * x
 
double 2 --- => 4
double 100 --- => 200
 
inRange min max x = min <= x && x <= max
 
inRange 10 15 11 --- => True
inRange 1 2 3 --- => False

Try defining them in your .hs file and running the functions in GHCi.

Types

If you defined the above functions in VSCode with the Haskell extension set up (If you haven't done this, I recommend reading Road to Haskeller #0 - Setting Up Environment and setting up the same environment on your device), you should see small strings appear just above your functions. These are the inferred types of the functions, but do not click them for now. Every value (a function is also a value) in Haskell has its type, and we can assign a type to anything like the following:

x :: <type>

Examples

Let's look at some examples to understand how types can be assigned in Haskell.

x :: Integer
x = 1
 
y :: Bool
y = True

The above shows how types can be assigned to variables. Let's see how types can be assigned to double and inRange.

double :: Integer -> Integer
double 1 --- => 2
double 1.2 --- => Type Error
 
inRange :: Float -> Float -> Float -> Bool
inRange 1.1 1.3 1.2 --- => True
inRange 1 3 2 --- => Type Error

Let's look at the double function first. The first part of the type definition specifies that the input has the type Integer (1, 2, 3, etc.). Then, the latter part defines that its output also has the type Integer. Notice here that we don't distinguish between input and output in the type definition. This is related to a very important concept in Haskell (Currying), but more on that much later.

The same goes for the inRange function. The types of the first three arguments are set to be Float and the output is set to be Bool (True or False). However, if you are following along by using the same VSCode setup, you might be wondering why you see a difference between the above and the inferred types displayed earlier. This is because the inferred types use the concept of typeclasses (like Num and Ord), which is outside the scope of this article. For now, always try using types, not typeclasses, that are often recommended by VSCode to learn more about types.

Bindings

Sometimes, we would like to have some kind of intermediary variables to clean up our code. However, the following is illegal.

inRange min max x =
  lower_bound = x >= min
  upper_bound = x <= max
  return lower_bound && upper_bound

How do we achieve the same thing without being scolded by Haskell? This is where bindings come in.

Let Bindings

The let bindings work like the following:

inRange min max x =
  let lower_bound = x >= min
      upper_bound = x <= max
  in
  lower_bound && upper_bound

We use let to define intermediary variables, and in to operate on those variables.

Where Bindings

Depending on the functions, we may want to use where bindings for better code readability.

inRange min max x = lower_bound && upper_bound
  where lower_bound = x >= min
        upper_bound = x <= max

We first define the final operations we want to perform with intermediary variables, and then specify what those variables mean after where.

If Statements

If you have experience coding in other programming languages, you might be wondering how to write an if statement. You can do so like the following:

inRange min max x = 
  if lower_bound then upper_bound else False
  where lower_bound = x >= min
        upper_bound = x <= max

This is pretty intuitive and easy to write and read. Today, we covered the essential components: functions, types, bindings, and if statements in Haskell. I hope you learned something from this article.

Exercises

From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.

Resources