Learn You a Haskell for Great Good! - Miran Lipovaca [45]
ghci> Map.fromList [("kima","greggs"),("jimmy","mcnulty"),("jay","landsman")]
fromList [("jay","landsman"),("jimmy","mcnulty"),("kima","greggs")]
When a map from Data.Map is displayed on the terminal, it’s shown as fromList and then an association list that represents the map, even though it’s not a list anymore.
If there are duplicate keys in the original association list, the duplicates are just discarded:
ghci> Map.fromList [("MS",1),("MS",2),("MS",3)]
fromList [("MS",3)]
This is the type signature of fromList:
Map.fromList :: (Ord k) => [(k, v)] -> Map.Map k v
It says that it takes a list of pairs of type k and v, and returns a map that maps from keys of type k to values of type v. Notice that when we were doing association lists with normal lists, the keys only needed to be equatable (their type belonging to the Eq type class), but now they must be orderable. That’s an essential constraint in the Data.Map module. It needs the keys to be orderable so it can arrange and access them more efficiently.
Now we can modify our original phoneBook association list to be a map. We’ll also add a type declaration, just because we can:
import qualified Data.Map as Map
phoneBook :: Map.Map String String
phoneBook = Map.fromList $
[("betty", "555-2938")
,("bonnie", "452-2928")
,("patsy", "493-2928")
,("lucille", "205-2928")
,("wendy", "939-8282")
,("penny", "853-2492")
]
Cool! Let’s load this script into GHCi and play around with our phoneBook. First, we’ll use lookup to search for some phone numbers. lookup takes a key and a map, and tries to find the corresponding value in the map. If it succeeds, it returns the value wrapped in a Just; otherwise, it returns a Nothing:
ghci> :t Map.lookup
Map.lookup :: (Ord k) => k -> Map.Map k a -> Maybe a
ghci> Map.lookup "betty" phoneBook
Just "555-2938"
ghci> Map.lookup "wendy" phoneBook
Just "939-8282"
ghci> Map.lookup "grace" phoneBook
Nothing
For our next trick, we’ll make a new map from phoneBook by inserting a number. insert takes a key, a value, and a map, and returns a new map that’s just like the old one, but with the key and value inserted:
ghci> :t Map.insert
Map.insert :: (Ord k) => k -> a -> Map.Map k a -> Map.Map k a
ghci> Map.lookup "grace" phoneBook
Nothing
ghci> let newBook = Map.insert "grace" "341-9021" phoneBook
ghci> Map.lookup "grace" newBook
Just "341-9021"
Let’s check how many numbers we have. We’ll use the size function from Data.Map, which takes a map and returns its size. This is pretty straightforward:
ghci> :t Map.size
Map.size :: Map.Map k a -> Int
ghci> Map.size phoneBook
6
ghci> Map.size newBook
7
The numbers in our phone book are represented as strings. Suppose we would rather use lists of Ints to represent phone numbers. So, instead of having a number like "939-8282", we want to have [9,3,9,8,2,8,2]. First, we’re going to make a function that converts a phone number string to a list of Ints. We can try to map digitToInt from Data.Char over our string, but it won’t know what to do with the dash! That’s why we need to get rid of anything in that string that isn’t a number. To do this, we’ll seek help from the isDigit function from Data.Char, which takes a character and tells us if it represents a digit. Once we’ve filtered our string, we’ll just map digitToInt over it.
string2digits :: String -> [Int]
string2digits = map digitToInt . filter isDigit
Oh, be sure to import Data.Char, if you haven’t already.
Let’s try this out:
ghci> string2digits "948-9282"
[9,4,8,9,2,8,2]
Very cool! Now, let’s use the map function from Data.Map to map string2digits over our phoneBook:
ghci> let intBook = Map.map string2digits phoneBook
ghci> :t intBook
intBook :: Map.Map String [Int]
ghci> Map.lookup "betty" intBook
Just [5,5,5,2,9,3,8]
The map from Data.Map takes a function and a map, and applies that function to each value in the map.
Let’s extend our phone book. Say that a person can have several numbers, and we have an association list set up like this:
phoneBook =
[("betty", "555-2938")
,("betty", "342-2492")