Learn You a Haskell for Great Good! - Miran Lipovaca [40]
import Data.List hiding (nub)
Another way of dealing with name clashes is to do qualified imports. Consider the Data.Map module, which offers a data structure for looking up values by key. This module exports a lot of functions with the same name as Prelude functions, such as filter and null. So if we imported Data.Map and then called filter, Haskell wouldn’t know which function to use. Here’s how we solve this:
import qualified Data.Map
Now if we want to reference Data.Map’s filter function, we must use Data.Map.filter. Entering just filter still refers to the normal filter we all know and love. But typing Data.Map in front of every function from that module is kind of tedious. That’s why we can rename the qualified import to something shorter:
import qualified Data.Map as M
Now to reference Data.Map’s filter function, we just use M.filter.
As you’ve seen, the . symbol is used to reference functions from modules that have been imported as qualified, such as M.filter. We also use it to perform function composition. So how does Haskell know what we mean when we use it? Well, if we place it between a qualified module name and a function, without whitespace, it’s regarded as just referring to the imported function; otherwise, it’s treated as function composition.
Note
A great way to pick up new Haskell knowledge is to just click through the standard library documentation and explore the modules and their functions. You can also view the Haskell source code for each module. Reading the source code of some modules will give you a solid feel for Haskell.
Solving Problems with Module Functions
The modules in the standard libraries provide many functions that can make our lives easier when coding in Haskell. Let’s look at some examples of how to use functions from various Haskell modules to solve problems.
Counting Words
Suppose we have a string that contains a bunch of words, and we want to know how many times each word appears in the string. The first module function we’ll use is words from Data.List. The words function converts a string into a list of strings where each string is one word. Here’s a quick demonstration:
ghci> words "hey these are the words in this sentence"
["hey","these","are","the","words","in","this","sentence"]
ghci> words "hey these are the words in this sentence"
["hey","these","are","the","words","in","this","sentence"]
Then we’ll use the group function, which also lives in Data.List, to group together words that are identical. This function takes a list and groups adjacent elements into sublists if they are equal:
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
But what happens if the elements that are equal aren’t adjacent in our list?
ghci> group ["boom","bip","bip","boom","boom"]
[["boom"],["bip","bip"],["boom","boom"]]
We get two lists that contain the string "boom", even though we want all occurrences of some word to end up in the same list. What are we to do? Well, we could sort our list of words beforehand! For that, we’ll use the sort function, which hangs its hat in Data.List. It takes a list of things that can be ordered and returns a new list that is like the old one, but ordered from smallest to largest:
ghci> sort [5,4,3,7,2,1]
[1,2,3,4,5,7]
ghci> sort ["boom","bip","bip","boom","boom"]
["bip","bip","boom","boom","boom"]
Notice that the strings are put in an alphabetical order.
We have all the ingredients for our recipe. Now we just need to write it down. We’ll take a string, break it down into a list of words, sort those words, and then group them. Finally, we’ll use some mapping magic to get tuples like ("boom", 3), meaning that the word "boom" occurs three times.
import Data.List
wordNums :: String -> [(String,Int)]
wordNums = map (\ws -> (head ws, length ws)) . group