user2730833
user2730833

Reputation: 183

improvement to my code for Haskell monads

I am working with Haskell and maybe monads but I am a little bit confused with them here is my code but I am getting error and I do not know how to improve my code.

doAdd :: Int -> Int -> Maybe Int    
doAdd x y = do 
result <- x + y
return result

Upvotes: 1

Views: 164

Answers (4)

Luis Casillas
Luis Casillas

Reputation: 30227

Let's look critically at the type of the function that you're writing:

doAdd :: Int -> Int -> Maybe Int

The point of the Maybe monad is to work with types that are wrapped with a Maybe type constructor. In your case, the two Int arguments are just plain Ints, and the + function always produces an Int so there is no need for the monad.

If instead, your function took Maybe Int as its arguments, then you could use do notation to handle the Nothing case behind the scenes:

doAdd :: Maybe Int -> Maybe Int -> Maybe Int
doAdd mx my = do x <- mx
                 y <- my
                 return (x + y)

example1 = doAdd (Just 1) (Just 3)   -- => Just 4
example2 = doAdd (Just 1) Nothing    -- => Nothing
example3 = doAdd Nothing (Just 3)    -- => Nothing
example4 = doAdd Nothing Nothing     -- => Nothing

But we can extract a pattern from this: what you are doing, more generically, is taking a function ((+) :: Int -> Int -> Int) and adapting it to work in the case where the arguments it wants are "inside" a monad. We can abstract away from the specific function (+) and the specific monad (Maybe) and get this generic function:

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f ma mb = do a <- ma
                    b <- mb
                    return (f a b)

Now with liftM2 you can write:

doAdd :: Maybe Int -> Maybe Int -> Maybe Int
doAdd = liftM2 (+)

The reason why I chose the name liftM2 is because this is actually a library function—you don't need to write it, you can import the Control.Monad module and you'll get it for free.

What would be a better example of using the Maybe monad? When you have an operation that, unlike +, can intrinsically can produce a Maybe result. One idea would be if you wanted to catch division by 0 mistakes. You could write a "safe" version of the div function:

-- | Returns `Nothing` if second argument is zero.
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

Now in this case the monad does become more useful:

-- | This function tests whether `x` is divisible by `y`.  Returns `Nothing` if
-- division by zero.
divisibleBy :: Int -> Int -> Maybe Bool
divisibleBy x y = do z <- safeDiv x y
                     let x' = z * y
                     return (x == x')

Another more interesting monad example is if you have operations that return more than one value—for example, positive and negative square roots:

 -- Compute both square roots of x.
allSqrt x = [sqrt x, -(sqrt x)]

 -- Example: add the square roots of 5 to those of 7.  
example = do x <- allSqrt 5
             y <- allSqrt 7
             return (x + y)

Or using liftM2 from above:

example = liftM2 (+) (allSqrt 5) (allSqrt 7)

So anyway, a good rule of thumb is this: never "pollute" a function with a monad type if it doesn't really need it. Your original doAdd—and even my rewritten version—are a violation of this rule of thumb, because what the function does is adding, but adding has nothing to do with Maybe—the Nothing handling is just a behavior that we add on top of the core function (+). The reason for this rule of thumb is that any function that does not use monads can be generically adapted to add the behavior of any monad you want, using utility functions like liftM2 (and many other similar utility functions).

On the other hand, safeDiv and allSqrt are examples where you can't really write the function you want without using Maybe or []; if you are dealing with a function like that, then monads are often a convenient abstraction for eliminating boilerplate code.

Upvotes: 8

bheklilr
bheklilr

Reputation: 54058

A better example might be

justPositive :: Num a => a -> Maybe a
justPositive x
    | x <= 0 = Nothing
    | otherwise = Just x

addPositives x y = do
    x' <- justPositive x
    y' <- justPositive y
    return $ x' + y'

This will filter out any non-positive values passed into the function using do notation

Upvotes: 3

jamshidh
jamshidh

Reputation: 12070

The use case you give doesn't justify do notation, but this is a more common use case- You can chain functions of this type together.

func::Int->Int->Maybe Int -- func would be a function like divide, which is undefined for division by zero

main = do
    result1 <- func 1 2
    result2 <- func 3 4
    result3 <- func result1 result2
    return result3

This is the whole point of monads anyway, chaining together functions of type a->m a.

When used this way, the Maybe monad acts much like exceptions in Java (you can use Either if you want to propagate a message up).

Upvotes: 0

Chuck
Chuck

Reputation: 237030

That isn't how you'd write that code. The <- operator is for getting a value out of a monad. The result of x + y is just a number, not a monad wrapping a number.

Do notation is actually completely wasteful here. If you were bound and determined to write it that way, it would have to look like this:

doAdd x y = do
  let result = x + y
  return result

But that's just a longwinded version of this:

doAdd x y = return $ x + y

Which is in turn equivalent to

doAdd x y = Just $ x + y

Which is how you'd actually write something like this.

Upvotes: 1

Related Questions