Question

I'm currently in the process of trying to learn Haskell, and ran into an odd issue regarding the Maybe monad which I can't seem to figure out.

As an experiment, I'm currently trying to take a string, convert each letter to an arbitrary number, and multiply/combine them together. Here's what I have so far:

lookupTable :: [(Char, Int)]
lookupTable = [('A', 1), ('B', 4), ('C', -6)]

strToInts :: String -> [Maybe Int]
strToInts = map lookupChar
    where 
        lookupChar :: Char -> Maybe Int
        lookupChar c = lookup c lookupTable

-- Currently fails
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x * y | (x, y) <- zip seq $ tail seq, x < y ]

main :: IO ()
main = do
    putStrLn $ show $ test $ strToInts "ABC"

When I try running this, it returns the following error:

test.hs:13:16:
    Could not deduce (Num (Maybe n)) arising from a use of `*'
    from the context (Num n, Ord n)
      bound by the type signature for
                 test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
      at test.hs:12:9-48
    Possible fix: add an instance declaration for (Num (Maybe n))
    In the expression: x * y
    In the expression: [x * y | (x, y) <- zip seq $ tail seq]
    In an equation for `test':
        test seq = [x * y | (x, y) <- zip seq $ tail seq]

I'm not 100% sure why this error is occurring, or what it exactly means, though I suspect it might be because I'm trying to multiply two Maybe monads together -- if I change the definition of test to the following, the program compiles and runs fine:

test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x | (x, y) <- zip seq $ tail seq, x < y ]

I also tried changing the type declaration to the below, but that didn't work either.

test :: (Num n, Ord n) => [Maybe n] -> [Num (Maybe n)]

I'm not really sure how to go about fixing this error. I'm fairly new to Haskell, so it might just be something really simple that I'm missing, or that I've structured everything completely wrong, but this is stumping me. What am I doing wrong?

Was it helpful?

Solution

Maybe does not have a num instance so you cannot multiply them together directly. You need to somehow apply the pure function to the values inside the context. This is exactly what applicative functors are for!

Applicative functors live in Control.Applicative:

import Control.Applicative

So you have this function and you want to apply it to 2 arguments in a context:

(*) :: Num a => a -> a -> a

You probably learned about fmap, it takes a function and applies it to a value in a context. <$> is an alias for fmap. When we fmap the pure function over the maybe value we get the following result:

(*) <$> Just 5 :: Num a => Maybe (a -> a)

So now we have maybe a function and we need to apply it to maybe a value, this is exactly what the applicative functor does. Its main operator is <*> which has the signature:

(<*>) :: f (a -> b) -> f a -> f b

When we specialize it we get the function we need:

(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

We apply it and the output is the number you expect.

(*) <$> Just 5 <*> Just 5 :: Num a => Maybe a

So to make your code compile you need to change your test function to use <$> and <*>, see if you can figure out how.

OTHER TIPS

Reite's answer is correct, and it's generally how I'd normally recommend handling it - however, it seems to me that you don't quite understand how to work with Maybe values; if so there is little sense looking at applicative functors right now.

The definition of Maybe is basically just

 data Maybe a = Nothing | Just a

Which basically means exactly what it sounds like when you read it in plain english "A value of type Maybe a is either the value Nothing for that type, or a value of the form Just a".

Now you can use pattern matching to work with that, with the example of lists:

 maybeReverse :: Maybe [a] -> Maybe [a]
 maybeReverse Nothing = Nothing
 maybeReverse (Just xs) = Just $ reverse xs

Which basically means "If the value is Nothing, then there's nothing to reverse, so the result is Nothing again. If the value is Just xs then we can reverse xs and wrap it with Just again to turn it into a Maybe [a] value).

Of course writing functions like this for every single function we ever want to use with a Maybe value would be tedious; So higher order functions to the rescue! The observation here is that in maybeReverse we didn't do all that much with reverse, we just applied it to the contained value and wrapped the result of that in Just.

So we can write a function called liftToMaybe that does this for us:

 liftToMaybe :: (a->b) -> Maybe a -> Maybe b
 liftToMaybe f Nothing = Nothing
 liftToMaybe f (Just a) = Just $ f a

A further observation we can make is that because functions are values, we can also have Maybe values of functions. To do anything useful with those we could again unwrap them... or notice we're in the same situation as in the last paragraph, and immediately notice that we don't really care what function exactly is in that Maybe value and just write the abstraction directly:

 maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
 maybeApply Nothing _ = Nothing
 maybeApply _ Nothing = Nothing
 maybeApply (Just f) (Just a) = Just $ f a

Which, using our liftToMaybe function above, we can simplify a bit:

 maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
 maybeApply Nothing _ = Nothing
 maybeApply (Just f) x = liftToMaybe f x

The <$> and <*> operators in Reite's answer are basically just infix names for liftToMaybe (which is also known as fmap) and maybeApply respectively; They have the types

(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b

You don't really need to know what the Functor and Applicative things are right now (although you should look into them at some point; They're basically generalizations of the above Maybe functions for other kinds of "context") - basically, just replace f with Maybe and you'll see that these are basically the same functions we've talked about earlier.

Now, I leave applying this to your original problem of multiplication to you (although the other answers kinda spoil it).

You are correct, the problem is that you are trying to multiply two Maybe values together, but (*) only works in instances of Num.

As it turn out, Maybe is an instance of the Applicative typeclass. This means that you can "lift" funcions that work with a type a to functions that work with a type Maybe a.

import Control.Applicative

Two functions provided by Applicative are:

pure :: a -> f a Puts a pure value in a "neutral context". For Maybe, this is Just.

(<*>) :: f (a -> b) -> f a -> f b Lets you apply a "function in a context" to two "values in a context".

So, suppose we have this pure computation:

(*) 2 3

Here are some analogous computations in a Maybe context:

Just (*) <*> Just 2 <*> Just 3
-- result is Just 6

pure (*) <*> pure 2 <*> pure 3
-- result is Just 6 -- equivalent to the above

pure (*) <*> pure 2 <*> Nothing
-- Nothing

Nothing <*> pure 2 <*> Just 3
-- Nothing

Nothing <*> Nothing <*> Nothing
-- Nothing

If the function or one of the argument is "missing", we return Nothing.

(<*>) is the explicit application operator when working with applicatives (instead of using a blank space, as when we work with pure values).

It's instructive to explore what (<*>) does with other instances of Applicative, like []:

ghci> [succ,pred] <*> pure 3
[4,2]
ghci> [succ,pred] <*> [3]
[4,2]
ghci> pure succ <*> [2,5]
[3,6]
ghci> [succ] <*> [2,5]
[3,6]
ghci> [(+),(*)] <*> pure 2 <*> pure 3
[5,6]
ghci> [(+),(*)] <*> [2,1] <*> pure 3
[5,4,6,3]
ghci> [(+),(*)] <*> [2,1] <*> [3,7]
[5,9,4,8,6,14,3,7]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top