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?
The Maybe monad represents computations which might "go wrong" by not returning a value.
One thing I noticed was that Tuple does not have a Monad instance. Which already extremely heavily restricts what we can make the Monad instance be.
monads are used to address the more general problem of computations (involving state, input/output, backtracking, ...) returning values: they do not solve any input/output-problems directly but rather provide an elegant and flexible abstraction of many solutions to related problems.
A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With