Where Are the Hashes, Haskell?

How to Model a Hash Data Structure in Haskell

Sara Khandaker
3 min readAug 6, 2021

It's true, Haskell doesn’t have hashes. In terms of data types with multiple values, Haskell offers only two: Lists and Tuples. Lists offer an ordered array of elements (all of the same type) and Tuples have a fixed number of elements that are immutable but can be of varying types. Neither of these offers the key-value pair data type that a hash or dictionary does.

Coming from a Javascript background, this was rather strange. After all, Javascript uses the “object” or hash data structure for almost everything… even an array in JavaScript is just a hash with the indices as the keys.

Below are two ways you can use model a key-value pair data structure and look up the values.

Using Tuples and Lists

The first way involves using Haskell’s Tuples and Lists. We can model a key-value pair as a Tuple:

('key', 'value')

And we can store multiple pairs in a List of these Tuples. This will essentially be our hash:

[('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]

Now to look up a value from a key. You can create a search function to find the matching values for a key:

search :: [(String, String)] -> String -> Maybe Answer
search [] key = Nothing
search ((k,v):xs) key = if key == k
then Just v
else search xs key

Actually, Haskell already has a built-in lookup function. It takes in a list of tuples and will return the values that match or nothing if there are no matches. See the example below:

lookup 'key3' [('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]Output: Just 'value3'

Using Data.Map

Another way to model a hash in Haskell is to use the Data.Map module. The Data.Map module is an implementation of map from keys to values using size-balanced binary trees under the hood. It's just a fancier (more optimized) version of what we were doing with the lists of tuples.

import Data.Map

First, we want to create a map. This will allow us to store key-value pairs and search in them. So a map from Data.Map is essentially a hash in that it stores key-value pairs. However, it's not like a JavaScript hash since it uses a binary tree and not hashing.

To create your map you can either convert a list of tuples like the one above using the fromList function or you can manually add each key-value pair using the insert function:

hash = [(1, "one"), (2, "two"), (3, "three")]

mapFromList = Data.Map.fromList hash

mapManual =
Data.Map.insert 2 "two" .
Data.Map.insert 1 "one" .
Data.Map.insert 3 "three" $ Map.empty

Now we have our map (or essentially hash but really a tree…). To search for values we can simply use the lookup function again with the key we are interested in.

lookup 2 mapFromListOutput: Just 'two'

Runtime Analysis

One thing to note is that the runtime of both these hash implementations. One of the benefits of using a hash/ dictionary structure is that they provide constant-time O(1) lookup, regardless of the size or number of key-value pairs. In the first Haskell implementation the runtime will be linear O(n) since you need to traverse the entire list and in the second implementation the lookup runtime will be logarithmic O(logn) since it uses a binary tree. Therefore, both these implementations are still less optimized than a standard hash in JavaScript.

References

--

--