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.
How to use Data.Map.lookup in the context of an Either monad?
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]
Solution 2
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 ValError
s 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.