April 4, 2012

Learning Haskell: Functions

In the previous article we learned how to represent a problem by using algebraic data types.

In this article I will talk about functions, purity, lazyness, type signatures and some tools like partial application and pattern matching.

Functions

Functions are the base of functional languages. In Haskell they receive 0 or more arguments and always return a value. Haskell syntax for functions is really simple:

numCardsInAPokerDeck = 53
numCards deck = length deck
isAPokerDeck deck = (numCards deck) (==) numCardsInAPokerDeck
biggerDeck d1 d2 = if (numCards d1) > (numCards d2)
                   then d1
                   else d2

Function application is left associative.

Purity

Haskell functions are pure. This means that they cannot alter the state of the program by producing side-effects. Any pure function is also idempotent so it must return always the same result for a given set of arguments.

How does Haskell achieve purity? - There are no variables: Functions can be used to define contant values but those can't be modified. - Immutable data types: All the builtin data types defined in Haskell are immutable. Instead of altering the state of an instance, copies representing the new one are returned.

Lazyness

Since functions are pure and hold no side-effects the computation can be performed at any time. This property allows programs to evaluate the code only when the result is needed.

dontEvaluateSecondParam a b = if True then a else b
powerOfTwo val = foldl (*) 1 (take val (repeat 2))

dontEvaluateSecondParam "foo" (powerOfTwo 10000) -- "foo"

Type signatures

Haskell can infer function's type signatures most of the times, although, is good to document all of them by providing the arguments and the return types.

You can learn a lot just by reading a function's type signature. For instance, imagine you want a function that takes a list and returns wether the list is empty or not. You can use Hoogle and search for [a] -> Bool, how cool is that!?

numCardsInAPokerDeck :: Int
numCardsInAPokerDeck = 53

numCards :: Deck -> Int
numCards deck = length deck

isAPokerDeck :: Deck -> Bool
isAPokerDeck deck = (==) (numCards deck) numCardsInAPokerDeck

Types signatures declarations are right associative!

So far, so good. But then, how can you define a function that takes more than one argument? The answer is simple and astonishing: you can't, Haskell functions can only take one argument. Fasten your belts, we are about to enter to higher order function nirvana!

Partial application

Haskell functions are considered Higher Order Functions. This means that a function can:

  • Take a function as an argument
  • Return a function

This feature is used to do partial application. Imagine you have a function that takes two numbers and adds them:

add' :: Int -> (Int -> Int)
add' a b = a + b

add' 2 3 -- 5

In Haskell you don't have multiple arguments, instead you use partial application. For instance, the add' function we just defined can be defined as "a function that takes a number, returns a function that takes another number and returns the addition of both". Inception!

Remember that type signatures are right associative. The previous one could be simplified:

add' :: Int -> Int -> Int
add' a b = a + b
addTwo = add' 2

addTwo 3 -- 5

Now that we know how partial application works, we can now add some type signatures to our previous examples:

biggerDeck :: Deck -> Deck -> Bool
biggerDeck d1 d2 = if (numCards d1) > (numCards d2)
                   then d1
                   else d2
firstDeck :: Deck
firstDeck = take 5 (repeat Joker)

secondDeck :: Deck
secondDeck = take 2 (repeat (Card Spade NA))

biggerThanFirstDeck :: Deck -> Bool
biggerThanFirstDeck = biggerDeck firstDeck

biggerThanFirstDeck secondDeck -- firstDeck
biggerDeck firstDeck secondDeck -- firstDeck

Pattern matching

Last tool I'm going to explain on this article is pattern matching.

Imagine we want a function that takes a PlayingCard as an argument and returns wether its a Joker or its not:

isJoker :: PlayingCard -> Bool
isJoker Joker = True
isJoker a = False

We can use _ if we don't need to assign the value:

isJoker :: PlayingCard -> Bool
isJoker Joker = True
isJoker _ = False

isJoker Joker            -- True
isJoker (Card Spades NA) -- False

We can "deconstruct" the values to match only certain parts:

isSuite :: Suite -> PlayingCard -> Bool
isSuite suite Joker = False
isSuite suite Card s n = suite == s

isDiamond = isSuite Diamond
isHeart = isSuite Heart
isSpade = isSuite Spade
isClub = isSuite Club

isDiamond Joker -- False
isDiamond Card Diamond N3 -- True
isDiamond Card Club N7 -- False

We can even "deconstruct" lists and tuples!

getFirstCard :: Deck -> PlayingCard
getFirstCard [] = error "OMG! The deck is empty!"
getFirstCard x:xs = x

isPair :: (PlayingCard, PlayingCard) -> Bool
isPair (Joker, b) = True
isPair (a, Joker) = True
isPair (a, b) = a == b