user7235699
user7235699

Reputation: 431

Functions as types in haskell

I have confusion using types which are functions.

Let say I want to implement a dictionary, which when given a and b returns Maybe b.

type Dict a b = a->Maybe b

How can I implement an insertion function for this dictionary?

insertDict :: (Eq a) => a -> b -> (Dict a b)-> (Dict a b)

I have come up with the following thing

insertDict x y mydict = \a->Just y

but it is not correct and will discard the previous dictionary.

Upvotes: 10

Views: 1951

Answers (2)

Jon Purdy
Jon Purdy

Reputation: 55069

Here’s one method you can use to help write such functions yourself. First, write the type signature:

insertDict :: (Eq k) => k -> v -> Dict k v -> Dict k v

I’ve used k and v for “key” and “value” here for clarity. Next, start by writing the implementation as a hole:

insertDict key value dict
  = _

The compiler (or GHCi) should give you a message like “Found hole: _ :: Dict k v […] Relevant bindings include: dict :: Dict k v, value :: v, key :: k. So here you see that you could just return dict because the type matches, but that would ignore key and value.

Since you know that Dict k v is a function type, you can make progress by adding a lambda with another hole to see what the compiler offers:

insertDict key value dict
  = \ key' -> _

Now we have _ :: Maybe v, value :: v, key' :: k, key' :: k, dict :: Dict k v. We could always return Just value, but as you observed, that doesn’t do what we want—it represents a dictionary that always answers “Yes, that key is in the dictionary and its value is value” for any key you ask about! (That is a useful thing to be able to represent, it’s just not the thing we’re writing.)

So it doesn’t look like we can make progress with just these—but wait, we also have an Eq k constraint! And the only two things we can compare are key and key', so let’s expand this into an if using ==:

insertDict key value dict
  = \ key' -> if key == key' then _1 else _2

Now the compiler reports _1 :: Maybe v and _2 :: Maybe v. What should we return in each of these cases? Let’s consider some examples of how this function is actually used—if you look up a key in a dictionary after inserting a key-value pair, you should of course find the value:

(insertDict key value dict) key == Just value
                                   ----------

So for _1 we can write Just value:

insertDict key value dict
  = \ key' -> if key == key' then Just value else _2
                                  ----------

If you look up a different key than the one you just inserted, then the most recently inserted key-value pair doesn’t matter; it should look up the key further on in the dictionary:

(insertDict key value dict) key' == dict key'  -- If key /= key'
                                    ---------

So for _2 we can write dict key':

insertDict key value dict
  = \ key' -> if key == key' then Just value else dict key'
                                                  ---------

And we’re done! :D

This combination of type-directed programming and equational reasoning is very helpful when writing in Haskell, especially for polymorphic functions like this that have a limited number of possible (sane) implementations.

Upvotes: 11

danidiaz
danidiaz

Reputation: 27766

You can use a "chain of responsibility" pattern: the insert function checks if the argument to the result Dict matches with its own key, otherwise it delegates on the previous Dict which received as argument.

type Dict a b = a -> Maybe b

insertDict :: (Eq a) => a -> b -> Dict a b -> Dict a b
-- Note that the k' is the argument of the result dict function
insertDict k v dict k' = if k == k' then Just v else dict k'

emptyDict :: Dict a b
emptyDict _ = Nothing

Some examples in ghci:

Λ insertDict 'a' (1::Int) emptyDict $ 'a'
Just 1
Λ insertDict 'b' 2 (insertDict 'a' (1::Int) emptyDict) $ 'a'
Just 1
Λ insertDict 'b' 2 (insertDict 'a' (1::Int) emptyDict) $ 'x'
Nothing

Representing maps as functions is good as a first approximation, but this representation has a number of drawbacks:

  • The complexity of searching for a value is linear in the number of keys.
  • Functions are pretty "opaque", which means you can't inspect or serialize the map as you could do if you had used a data type.

Upvotes: 17

Related Questions