4 mins read

Learn Haskell 2: Function and Data Type

Category: Functional Programming Programming

Part 1: function, currying, pattern matching and function composition

It makes sense that if some pattern/idea is used a lot, then it should be denoted with minial syntax. It follows that Haskell, famous for being functional, doesn’t even have a keyword for defining functions. To define a function, start with function name (add for example), followed by argument list separated by spaces, followed by = and then the function body:

add x y = x + y
print $ add 1 2
## 3

Note that the $ sign seperate print and add so the compiler knows add 1 2 should be treated/evaluated as one group and then the result goes into print.

Functions are automatically curried, meaning that we can pass in fewer arguments and get back a more specific function.

For example, if we want to define a function that adds 2 to a number, we can do: add2 = add 2. That is, the type of add is Int -> Int -> Int, or in English, add takes an Int and returns a function that takes an Int and returns an Int. I use Int as a concrete example, actually the add we defined is polymophic in that it can also operate on Double, Float etc.

add x y = x + y
add2 = add 2
print $ add2 3
## 5

Functions support pattern matching. For example, to calculate the distance to origin of a point, (3, 4), we can do

point = (3, 4) -- a tuple
distToOrigin (x, y) = sqrt $ x^2 + y^2 -- pattern match the elements of tuple
distToOrigin point
## 5.0

Function composition is denoted by . (pronouned as “after”). For example, we can compose sqrt and distToOriginSquared to compute distance to origin:

point = (6, 8)
distToOriginSquared (x, y) = x^2 + y^2
distToOrigin = sqrt . distToOriginSquared
distToOrigin point
## 10.0

Part 2: data types

We can define a type with data keyword, followed by the type constructor (the name of which is your choice).

For example, we can defined a list type that always has at least one element:

data NonEmptyList a = SingletonList a | Cons a (NonEmptyList a)

Here, NonEmptyList on the left hand side (as well as right hand side) of the type definiton is an example of type constructor (as it’s name suggests, it constructs a new type), where as SingletonList and Cons on the right hand side of the type definition are data constructors, they are used to construct/instantiate data of that perticular type.

I guess if you’re familiar with java, python or other OOP languages, it helps to think of the type constructor as class definition, e.g.:

public class NonEmptyList<A> {
  ...
}

whereas data constructors are equivalent to the constructor methods, except that in java constructors all have to have the same name as the class. e.g.:

public class NonEmptyList<A> {

  public NonEmptyList(A a) {
    // constructor to construct a "Singleton" NonEmptyList
    ...
  } 
  
  public NonEmptyList(A a, NonEmptyList<A> nonEmptyList) {
    // constructor to construct a "Cons" NonEmptyList
    ...
  }
  
}

Another similarity between data constructors in haskell and constructor methods in other languages (java) is that they are both functions: constructor in java is just a function that takes some arguments and returns/instantiates a concrete instance of the class, data constructors in haskell also return a value of different type.

Back to the haskell NonEmptyList definition, | means the NonEmptylist is either a SingletonList that contains a single element of type a, or a Cons that contains an element of type a and another element of type NonEmptyList a (note that NonEmptyList a is a type constructed by the type constructor NonEmptyList).

With this new data type, we can safely take the head of the nonempty list, without worry about any runtime exception being throw, by pattern matching against the two cases with the data constructor SingletonList and Cons:

:{
  head (SingletonList x) = x
  head (Cons x _) = x    -- `_` means we don't care the value of the second parameter of the Cons
:}

aSingletonList = SingletonList 1
head aSingletonList

aComplicatedList = Cons 10 (Cons 20 (SingletonList 30))
head aComplicatedList
## 1
## 10

Note that because I’m using ghc -e to compile the snippets (e stands for expression), I need to use :{ ... :} to tell ghc that the two cases of my function definition should be evaluated together, otherwise ghc -e will evaluate expressions line by line and throw a Non-exhaustive patterns exception after it evaluates the first case.