Skip to content

Functions

Function application

Given a function, such as:

add1 x = x + 1

we can call it on input as follows:

add1 2
> 3

One can also write add1(2), but Haskell convention is to avoid brackets where possible.

Note

One can choose any name for an argument (provided it is alphanumeric and starts with a lowercase letter).

If you have named something else by the same name elsewhere, that is fine:

x = True
add1 x = x + 1

The occurrence of x on the right of line 2 refers to the occurrence on the left of line 2, not the occurrence on line 1.

Arguments

In Haskell, a function has a single argument, but that argument can be a tuple of multiple values:

exampleFunc :: (Int, Int) -> Int
exampleFunc (i1, i2) = i1 + i2

Currying

Another approach to taking multiple arguments, more commonly used, is shown here:

exampleFunc :: Int -> (Int -> Int) -- (1)!
exampleFunc i1 i2 = i1 + i2
  1. By convention in Haskell, Int -> Int -> Int means Int -> (Int -> Int), not (Int -> Int) -> Int.

As the type signature indicates, the first argument is an integer, but the output is a function which takes a second integer and returns an integer.

Accordingly, we can apply exampleFunc to an integer, say 5, and obtain an "add 5" function:

> :t exampleFunc 4
(Int -> Int)

> add4 = exampleFunc 4
> add4 5
9

-- or directly
> (exampleFunc 4) 5 -- (1)!
9
  1. By convention in Haskell, exampleFunc 4 5 means (exampleFunc 4) 5.

This is often referred to as partial application. exampleFunc would be described as a curried function.

Hint

When you see a function with a type like Type1 -> Type2 -> Type3 -> Type4 (which brackets as: Type1 -> (Type2 -> (Type3 -> Type4)) ), you can think of it as taking Type1, Type2 and Type3 as inputs, and outputting Type4.

Partial application for types

The same holds for types and their kinds:

repl example
> :kind Either
Either :: * -> (* -> *)
> :kind Either Bool
Either Bool :: * -> *
> :kind Either Bool Int
Either Bool Int :: *

Pattern matching on sum types

When defining a function, you can pattern match:

exampleFunc :: Either Int Bool -> Int
exampleFunc (Left i) = i -- (1)!
exampleFunc (Right True) = 1
exampleFunc (Right _) = 0 -- (2)!
  1. i matches any Int. It has to be an Int because the input has type Either Int Bool (Int is on the left).

  2. The underscore _ matches Bool. It has to be a Bool because the input has type Either Int Bool (Bool is on the right).

Here is how the function behaves:

> exampleFunc (Left 6)
6
> exampleFunc (Right True)
1
> exampleFunc (Right False)
0

Here, (Left i), (Right True) and (Right _) are all patterns.

The patterns are matched top-down. For example, if the function were:

1
2
3
4
exampleFunc :: Either Int Bool -> Int
exampleFunc (Left i) = i 
exampleFunc (Right _) = 0 
exampleFunc (Right True) = 1

Then:

> exampleFunc (Right True)
0

because line 3 would be matched before line 4 was reached.

Pattern matching on product types

The above example concerns a type that is either a Right True or a Right False or a Left 0, or a Left 1...In other words, it is a sum type.

Pattern matching also works with products (and products of sums, sums of products, and so on):

repl example
> ex = (True, False)
> (b1, b2) = ex
> b1
True
> b2
False

-- with a custom type
> data Entity = Sq Int Bool
> entity = Sq 4 False
> Sq i b = entity
> i
4
> b
False

This works also in functions:

repl example
> data Color = White | Black deriving Show
> data PieceType = Bishop | Knight deriving Show
> data Piece = P PieceType Color deriving Show
> getColor (P _ c) = c
> getColor (P Bishop White)
White

Note

Patterns can be arbitrarily deeply nested, as in:

data Color = Black | White
data Piece = Bishop Color | Knight Color 

getColor :: Either Bool Piece -> Maybe Color
getColor (Right (Bishop c)) = Just c
getColor (Right (Knight c)) = Just c
getColor (Left _) = Nothing

And also used with recursive types

repl example
> data BinTree = Leaf Char | Branches BinTree BinTree
> leftRightLeaf (Branches (Branches _ (Leaf c))  _) = c
> tree = Branches (Branches (Leaf 'a') (Leaf 'c')) (Leaf 'b')  
> leftRightLeaf tree
'c'

Pattern matching lists

Pattern matching also applies to lists, since they are recursive types:

repl example
> ls@(head:tail) = [1,2,3]
> head
1
> tail
[2,3]
> ls
[1,2,3]

Tip

A classic example of returning an error when getting the first element of an empty list:

getFirstElement :: [b] -> Either String b
getFirstElement ( x : _ ) = Right x
getFirstElement [] = Left "the list is empty"

Using @ in patterns

repl example
-- first example
> let whole@(a,b) = ('a', True)
> a
'a'
> b
True
> whole
('a',True)

-- second example
> let whole@(a, left@(b, c)) = ('a', (True, ()))
> a
'a'
> b
True
> c
()
> whole
('a',(True,()))
> left
(True,())

And in a function:

exampleFunc :: (Int, Bool) -> (Int, Bool)
exampleFunc whole@(int,bool) 
    | even int = whole
    | otherwise = (int - 1, not bool)

Last update: January 18, 2023
Created: January 7, 2023

Comments