Reputation: 431
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
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
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:
Upvotes: 17