Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting a Maybe value in IO

Given the following:

> (liftM2 fromMaybe) (ioError $ userError "OOPS") (return $ Just "ok")

ghci gives me

*** Exception: user error (OOPS)

Of course, fromMaybe is working correctly:

> (liftM2 fromMaybe) (return $ "not me") (return $ Just "ok")
"ok"

But it seems that the IO operation is being carried out and then discarded:

> (liftM2 fromMaybe) (putStrLn "computing.." >> "discarded") (return $ Just "ok")
computing..
"ok"

Why is this happening? Is there any way to make the IO monad lazier?

Specifically, given value :: IO (Maybe a) what's a (clean, concise) way to say

result <- (liftM2 fromMaybe) err value

and have it unpack result or throw an IOError accordingly?

like image 678
So8res Avatar asked Dec 16 '11 22:12

So8res


3 Answers

I don't know that making IO lazier is the right direction here. What you seem to want to do is first get at the Maybe, then eliminate it. This can be written several ways, here's one option:

test :: IO (Maybe a) -> IO a
test = (>>= maybe (ioError $ userError "oops") return)
like image 174
Anthony Avatar answered Nov 12 '22 15:11

Anthony


If you translate from liftM2 to do-notation, it's obvious why your code fails:

do x <- ioError $ userError "OOPS"
   y <- return $ Just "ok"
   return $ fromMaybe x y

This will never go past the first line, as it's unconditionally throwing an exception.

Anthony's suggestion will work fine, but if you don't care about the specific exception thrown, you can also use pattern matching:

do Just result <- value

If the pattern doesn't match, this will call fail, which in the case of the IO monad throws an exception.

> Just x <- return Nothing
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:0-5)
like image 26
hammar Avatar answered Nov 12 '22 14:11

hammar


what's a (clean, concise) way to ... unpack [the] result or throw an IOError accordingly?

I recommend you avoid relying on throwing errors. Instead, handle the "error" explicitly:

maybeM :: Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b
maybeM err f value = do
  x <- value
  case x of
    Just y  -> f y
    Nothing -> err

-- This can be written simply as:
maybeM err f value = do
  x <- value
  maybe err f x

-- or even shorter! This is starting to look like Anthony's answer :)
maybeM err f value = value >>= maybe err f

The function's inputs and types should speak for themselves. You use it by giving it an action to perform for the Nothing case, or a function to perform on the value inside for the Just case. For your particular inputs this would look like:

maybeM (ioError $ userError "OOPS") return (return $ Just "ok")

So if you absolutely must, then the "concise way to unpack the result or throw an IOError" would be:

-- compare to fromJust, a function to be avoided
fromJustIO :: IO (Maybe a) -> IO a
fromJustIO = maybeM (ioError $ userError "OOPS") return

Notice how the type signature for this is practically Maybe a -> a, which is the essence of magicMonadUnwrap :: Monad m => m a -> a, which should set off some red flags. However, you can use this atrocity in a simple manner:

result <- fromJustIO value

Although again, I strongly discourage the use of exceptions here. Try handling errors in a more elegant way than simply exploding, by using maybeM and providing an IO action to execute in the event of failure.

like image 5
Dan Burton Avatar answered Nov 12 '22 15:11

Dan Burton