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