Reputation: 23
Is there a way to write do
notation for a monad in a function which the return type isn't of said monad?
I have a main function doing most of the logic of the code, supplemented by another function which does some calculations for it in the middle. The supplementary function might fail, which is why it is returning a Maybe
value. I'm looking to use the do
notation for the returned values in the main function. Giving a generic example:
-- does some computation to two Ints which might fail
compute :: Int -> Int -> Maybe Int
-- actual logic
main :: Int -> Int -> Int
main x y = do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
-- does some Int calculation to first, second and third
What I intend is for first
, second
, and third
to have the actual Int
values, taken out of the Maybe
context, but doing the way above makes Haskell complain about not being able to match types of Maybe Int
with Int
.
Is there a way to do this? Or am I heading towards the wrong direction?
Pardon me if some terminology is wrongly used, I'm new to Haskell and still trying to wrap my head around everything.
EDIT
main
has to return an Int
, without being wrapped in Maybe
, as there is another part of the code using the result of main
as Int
. The results of a single compute
might fail, but they should collectively pass (i.e. at least one would pass) in main
, and what I'm looking for is a way to use do
notation to take them out of Maybe
, do some simple Int
calculations to them (e.g. possibly treating any Nothing
returned as 0
), and return the final value as just Int
.
Upvotes: 2
Views: 322
Reputation: 476709
Well the signature is in essence wrong. The result should be a Maybe Int
:
main :: Int -> Int -> Maybe Int
main x y = do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
return (first + second + third)
For example here we return (first + second + third)
, and the return
will wrap these in a Just
data constructor.
This is because your do
block, implicitly uses the >>=
of the Monad Maybe
, which is defined as:
instance Monad Maybe where
Nothing >>=_ = Nothing
(Just x) >>= f = f x
return = Just
So that means that it will indeed "unpack" values out of a Just
data constructor, but in case a Nothing
comes out of it, then this means that the result of the entire do
block will be Nothing
.
This is more or less the convenience the Monad Maybe
offers: you can make computations as a chain of succesful actions, and in case one of these fails, the result will be Nothing
, otherwise it will be Just result
.
You can thus not at the end return an Int
instead of a Maybe Int
, since it is definitely possible - from the perspective of the types - that one or more computations can return a Nothing
.
You can however "post" process the result of the do
block, if you for example add a "default" value that will be used in case one of the computations is Nothing
, like:
import Data.Maybe(fromMaybe)
main :: Int -> Int -> Int
main x y = fromMaybe 0 $ do
first <- compute x y
second <- compute (x+2) (y+2)
third <- compute (x+4) (y+4)
return (first + second + third)
Here in case the do
-block thus returns a Nothing
, we replace it with 0
(you can of course add another value in the fromMaybe :: a -> Maybe a -> a
as a value in case the computation "fails").
If you want to return the first element in a list of Maybe
s that is Just
, then you can use asum :: (Foldable t, Alternative f) => t (f a) -> f a
, so then you can write your main
like:
-- first non-failing computation
import Data.Foldable(asum)
import Data.Maybe(fromMaybe)
main :: Int -> Int -> Int
main x y = fromMaybe 0 $ asum [
compute x y
compute (x+2) (y+2)
compute (x+4) (y+4)
]
Note that the asum
can still contain only Nothing
s, so you still need to do some post-processing.
Upvotes: 8
Reputation: 3018
I have two interpretations of your question, so to answer both of them:
Maybe Int
values that are Just n
to get an Int
To sum Maybe Int
s while throwing out Nothing
values, you can use sum
with Data.Maybe.catMaybes :: [Maybe a] -> [a]
to throw out Nothing
values from a list:
sum . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]
Maybe Int
value that's Just n
as an Int
To get the first non-Nothing
value, you can use catMaybes
combined with listToMaybe :: [a] -> Maybe a
to get Just
the first value if there is one or Nothing
if there isn't and fromMaybe :: a -> Maybe a -> a
to convert Nothing
to a default value:
fromMaybe 0 . listToMaybe . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]
If you're guaranteed to have at least one succeed, use head
instead:
head . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]
Upvotes: 1
Reputation: 747
Willem's answer is basically perfect, but just to really drive the point home, let's think about what would happen if you could write something that allows you to return an int.
So you have the main
function with type Int -> Int -> Int
, let's assume an implementation of your compute
function as follows:
compute :: Int -> Int -> Maybe Int
compute a 0 = Nothing
compute a b = Just (a `div` b)
Now this is basically a safe version of the integer division function div :: Int -> Int -> Int
that returns a Nothing
if the divisor is 0
.
If you could write a main function as you like that returns an Int
, you'd be able to write the following:
unsafe :: Int
unsafe = main 10 (-2)
This would make the second <- compute ...
fail and return a Nothing
but now you have to interpret your Nothing
as a number which is not good. It defeats the whole purpose of using Maybe
monad which captures failure safely. You can, of course, give a default value to Nothing
as Willem described, but that's not always appropriate.
More generally, when you're inside a do
block you should just think inside "the box" that is the monad and don't try to escape. In some cases like Maybe
you might be able to do unMaybe
with something like fromMaybe
or maybe
functions, but not in general.
Upvotes: 3