Question

I'm trying to use Data.Map to map strings to functions. The issue I'm having is that the main error handling in my program uses an Either monad, and Map.lookup will return Maybe ([SomeVal] -> Either ValError SomeVal). How can I make Map.lookup play nicely with the Either monad in this case?

apply :: String -> [SomeVal] -> Either ValError SomeVal
apply s args = (Map.lookup s prims) >>= \fn -> fn args

prims :: Map String ([SomeVal] -> Either ValError SomeVal)
prims = Map.fromList
    [("key", function)
    ,("key2", function2)
    ]

::> apply "key" [val1, val2, val3]
Was it helpful?

Solution 2

The other answers have explained how to turn Maybe into Either in general. However for Data.Map specifically, instead of Map.lookup, you can use Map.findWithDefault, which doesn't wrap in a Maybe and allows you to choose what to do with non-existing keys.

OTHER TIPS

The Control.Error package has a nice function

note :: e -> Maybe a -> Either e a
note e Nothing  = Left e
note _ (Just a) = Right a

which is useful for "upgrading" Maybe-like failures to Either-like ones.

lookupNote :: k -> Map.Map k v -> Either ValError v
lookupNote k = note (Missing k) . lookup k

It also has many others like this for mapping between transformers of Either and Maybe and generic MonadPlus instances. I highly recommend that package.

That depends on how you want to handle the Nothing case. If you want to assert that the key will always be there (a bad idea)

import Data.Maybe
apply s args = (prims ! s) args

But the smarter method is to have a sane value in the case where nothing is found. Likely you'll want some ValError constructor

ValError = ...
         | NoKey String
         ...
apply s args = maybe (Left $ NoKey s) ($args) $ M.lookup s prims

Or want to just keep the Maybe

apply :: String -> [SomeVal] -> Maybe (Either ValError SomeVal)

Either of these methods provide much saner semantics in the case where the key wasn't found. Which you choose is mostly preference, if you intend to make apply a pretty fundamental part of your API so much so that ValErrors should know about it, then 1 works great.

Otherwise, 2 is more painful to use, but doesn't require changing any existing code which is pleasant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top