Learn You a Haskell for Great Good! - Miran Lipovaca [41]
We used function composition to make our final function. It takes a string, such as "wa wa wee wa", and then applies words to that string, resulting in ["wa","wa","wee","wa"]. Then sort is applied to that, and we get ["wa","wa","wa","wee"]. Applying group to this result groups adjacent words that are equal, so we get a list of lists of strings: [["wa","wa","wa"],["wee"]]. Then we map a function that takes a list and returns a tuple, where the first component is the head of the list and the second component is its length, over the grouped words. Our final result is [("wa",3),("wee",1)].
Here’s how we could write this function without function composition:
wordNums xs = map (\ws -> (head ws,length ws)) (group (sort (words xs)))
Wow, parentheses overload! I think it’s easy to see how function composition makes this function more readable.
Needle in the Haystack
For our next mission, should we choose to accept it, we will make a function that takes two lists and tells us if the first list is wholly contained anywhere in the second list. For instance, the list [3,4] is contained in [1,2,3,4,5], whereas [2,5] isn’t. We’ll refer to the list that’s being searched as the haystack and the list that we’re searching for as the needle.
For this escapade, we’ll use the tails function, which dwells in Data.List. tails takes a list and successively applies the tail function to that list. Here’s an example:
ghci> tails "party"
["party","arty","rty","ty","y",""]
ghci> tails [1,2,3]
[[1,2,3],[2,3],[3],[]]
At this point, it may not be obvious why we need tails at all. Another example will clarify this.
Let’s say that we’re searching for the string "art" inside the string "party". First, we use tails to get all the tails of the list. Then we examine each tail, and if any one starts with the string "art", we’ve found the needle in our haystack! If we were looking for "boo" inside "party", no tail would start with the string "boo".
To see if one string starts with another, we’ll use the isPrefixOf function, which is also found in Data.List. It takes two lists and tells us if the second one starts with the first one.
ghci> "hawaii" `isPrefixOf` "hawaii joe"
True
ghci> "haha" `isPrefixOf` "ha"
False
ghci> "ha" `isPrefixOf` "ha"
True
Now we just need to check if any tail of our haystack starts with our needle. For that, we can use the any function from Data.List. It takes a predicate and a list, and it tells us if any element from the list satisfies the predicate. Behold:
ghci> any (> 4) [1,2,3]
False
ghci> any (=='F') "Frank Sobotka"
True
ghci> any (\x -> x > 5 && x < 10) [1,4,11]
False
Let’s put these functions together:
import Data.List
isIn :: (Eq a) => [a] -> [a] -> Bool
needle `isIn` haystack = any (needle `isPrefixOf`) (tails haystack)
That’s all there is to it! We use tails to generate a list of tails of our haystack and then see if any of them starts with our needle. Let’s give it a test run:
ghci> "art" `isIn` "party"
True
ghci> [1,2] `isIn` [1,3,5]
False
Oh, wait a minute! It turns out that the function that we just made is already in Data.List! Curses! It’s called isInfixOf, and it does the same work as our isIn function.
Caesar Cipher Salad
Gaius Julius Caesar has entrusted upon us an important task. We must transport a top-secret message to Mark Antony in Gaul. Just in case we get captured, we’re going to use some functions from Data.Char to be a bit sneaky and encode messages by using the Caesar cipher.
The Caesar cipher is a primitive method of encoding messages by shifting each character by a fixed number of positions in the alphabet. We can easily create a sort of Caesar cipher of our own, and we won’t constrict ourselves to the alphabet—we’ll use the whole range of Unicode characters.
To shift characters forward and backward in the alphabet, we’re going to use the Data.Char module’s ord and chr functions, which convert characters to their corresponding numbers and vice versa:
ghci> ord 'a'
97
ghci> chr 97
'a'
ghci> map ord "abcdefgh"
[97,98,99,100,101,102,103,104]