Saturday 30 April 2016

Haskell: Pattern Matching


Patterns are very useful, to write clear and precise applications. In pattern matching, we attempt to match values against patterns, bind variables to successful matches.

You can perform pattern matching on any kind of data like tuples, lists, numbers, String etc., For Example, You want to give points to students based on their scores.
Grade
Points
1
10
2
9
3
8
Other
5

points.hs
pointsforGrade 1 = 10
pointsforGrade 2 = 9
pointsforGrade 3 = 8
pointsforGrade x = 5

*Main> :load points.hs
[1 of 1] Compiling Main             ( points.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> pointsforGrade 1
10
*Main> 
*Main> pointsforGrade 54
5
*Main> 
*Main> pointsforGrade 3
8
*Main>


When you call pointsforGrade, the patterns will be checked from top to bottom and when it matches to a pattern, corresponding body is executed.

'pointsforGrade 1' matches to value 10 'pointsforGrade 2' matches to value 9, 'pointsforGrade 3' matches to value 8, any other arguments to the function 'pointsforGrade' are matches to value 5.

Without pattern matching, we have to write the code using if-else statement (or) by using guards like below. Most of the times myself, I prefers write code using pattern matching and guards.

points.hs
pointsforGrade x
    | (x == 1) = 10
    | (x == 2) = 9
    | (x == 3) = 8
    | otherwise  = 5

*Main> :load points.hs
[1 of 1] Compiling Main             ( points.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> pointsforGrade 1
10
*Main> 
*Main> pointsforGrade 4
5


Since patterns are evaluated from top to bottom, make sure you write specific patterns first, generic patterns next. For example, if you write the code like below, whatever you pass as argument to the function pointsforGrade, it always returns value 5.

points.hs
pointsforGrade x = 5
pointsforGrade 1 = 10
pointsforGrade 2 = 9
pointsforGrade 3 = 8

*Main> :load points.hs
[1 of 1] Compiling Main             ( points.hs, interpreted )

points.hs:1:1: Warning:
    Pattern match(es) are overlapped
    In an equation for pointsforGrade:
        pointsforGrade 1 = ...
        pointsforGrade 2 = ...
        pointsforGrade 3 = ...
Ok, modules loaded: Main.
*Main> 
*Main> pointsforGrade 1
5
*Main> pointsforGrade 2
5
*Main> pointsforGrade 100
5


Observe above snippet, Haskell generates a warning message "patterns are overlapped". Always specify the most specific ones first and then the more general ones later.

What if don’t specify general pattern?
Suppose if you specified most specific patterns and forgot to specify the general one, then pattern match will thrown an exception, when you try to match for any non-specific value.

points.hs
pointsforGrade 1 = 10
pointsforGrade 2 = 9
pointsforGrade 3 = 8

*Main> :load points.hs
[1 of 1] Compiling Main             ( points.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> pointsforGrade 1
10
*Main> 
*Main> pointsforGrade 3
8
*Main> 
*Main> pointsforGrade 5
*** Exception: points.hs:(1,1)-(3,20): Non-exhaustive patterns in function pointsforGrade


Observe above snippet, when i called 'pointsforGrade 5', it throws an exception; it is because there is no matched pattern for the value 5. So whenever you are using patterns, always include a catch-all pattern so that our program doesn't crash if we get some unexpected input.

Applying pattern matching to Lists
[] matches to empty list, pattern like x:tempList bind the head of list to x, remaining elements of list to tempList.

listPattern.hs
getHead (x:xs) = x
getSecond (x:y:xs) = y
getTail (x:xs) = xs

*Main> :load listPattern.hs
[1 of 1] Compiling Main             ( listPattern.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> getHead [2, 3, 5, 7, 11]
2
*Main> 
*Main> getSecond [2, 3, 5, 7, 11]
3
*Main> 
*Main> getTail [2, 3, 5, 7, 11]
[3,5,7,11]


Following function displays different messages based on the number of elements in the list.

Number of elements in List
Message
0
The list is empty
1
List has one element
2
List has two elements
>2
List has more than two elements

listInfo.hs
info [] = "The list is empty"
info (x:[]) = "List has one element"
info (x:y:[]) = "List has two elements"
info(x:y:_) = "List has more than two elements"

*Main> :load listInfo.hs
[1 of 1] Compiling Main             ( listInfo.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> info []
"The list is empty"
*Main> 
*Main> info [2, 3, 5, 7]
"List has more than two elements"
*Main> 
*Main> info [2, 3]
"List has two elements"
*Main> 
*Main> info [2]
"List has one element"


Find Number of elements in the list
listLength.hs
listLength ([]) = 0
listLength (x:xs) = 1 + length (xs)

*Main> :load listLength.hs
[1 of 1] Compiling Main             ( listLength.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> listLength []
0
*Main> 
*Main> listLength [2, 3, 5, 7]
4
*Main> 
*Main> listLength [2, 3, 5, 7, 11]
5


Find sum of elements in list
sumOfElements.hs
sumOfElements ([]) = 0
sumOfElements (x:xs) = x + sumOfElements (xs)

*Main> :load sumOfElements.hs
[1 of 1] Compiling Main             ( sumOfElements.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> sumOfElements []
0
*Main> 
*Main> sumOfElements [2, 3, 5]
10
*Main> 
*Main> sumOfElements [2, 3, 5, -10]
0


Find sum of squares of all elements in the list.
sumOfSquares.hs
square x = x * x

sumOfSquares [] = 0
sumOfSquares (x:xs) = square (x) + sumOfSquares (xs)

Prelude> :load sumOfSquares.hs
[1 of 1] Compiling Main             ( sumOfSquares.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> sumOfSquares []
0
*Main> sumOfSquares [2, 3]
13
*Main> sumOfSquares [2, 3, 5, -10]
138


Multiply each element of list by 9
multiply.hs
multiplyBy9 x = (9 * x)

map1 fun [] = []
map1 fun (x:xs) = (multiplyBy9 x) : (map1 fun xs)

*Main> :load multiply.hs
[1 of 1] Compiling Main             ( multiply.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> map1 multiplyBy9 [2, 3, 5, 7]
[18,27,45,63]
*Main> 
*Main> map1 multiplyBy9 [2, 3, 5, 7, -7, 0]
[18,27,45,63,-63,0]


Drop first three elements of a list
drop.hs
dropEle (x:y:z:xs) = xs
dropEle (xs) = xs

*Main> :load drop.hs
[1 of 1] Compiling Main             ( drop.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> dropEle [2]
[2]
*Main> 
*Main> dropEle [2, 3]
[2,3]
*Main> 
*Main> dropEle [2, 3, 5]
[]
*Main> 
*Main> dropEle [2, 3, 5, 7]
[7]
*Main> 
*Main> dropEle [2, 3, 5, 7, 11]
[7,11]




Previous                                                 Next                                                 Home

No comments:

Post a Comment