6

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?

4

3 回答 3

13

Maybe 没有 num 实例,因此您不能将它们直接相乘。您需要以某种方式将纯函数应用于上下文中的值。这正是应用函子的用途!

应用函子存在于 Control.Applicative 中:

import Control.Applicative

所以你有这个函数,你想将它应用于上下文中的 2 个参数:

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

您可能了解了 fmap,它接受一个函数并将其应用于上下文中的一个值。<$>是 fmap 的别名。当我们将纯函数映射到可能的值上时,我们得到以下结果:

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

所以现在我们可能有一个函数,我们需要将它应用到一个值上,这正是 applicative functor 所做的。它的主要运算符是<*>具有签名的:

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

当我们专门化它时,我们得到了我们需要的功能:

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

我们应用它,输出是您期望的数字。

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

因此,要使您的代码编译,您需要更改要使用的测试函数<$>and <*>,看看您是否可以弄清楚如何。

于 2014-03-08T11:11:11.057 回答
6

Reite 的回答是正确的,这通常是我通常建议的处理方式 - 但是,在我看来,您不太了解如何处理Maybe值;如果是这样,那么现在查看应用函子就没有什么意义了。

的定义Maybe基本上就是

 data Maybe a = Nothing | Just a

这基本上意味着当你用简单的英语阅读它时听起来像“类型Maybe a的值要么是Nothing该类型的值,要么是表单的值Just a”。

现在您可以使用模式匹配来处理它,以列表为例:

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

这基本上意味着“如果值为Nothing,则没有什么可以反转的,所以结果Nothing又是。如果值是,Just xs那么我们可以再次将其reverse xs包装以将其转换为值)。JustMaybe [a]

当然,为我们想要使用的每个函数编写这样的函数Maybe是很乏味的。所以高阶函数来救援!这里的观察是,maybeReverse我们并没有对 做太多事情reverse,我们只是将它应用于包含的值并将结果包装在 中Just

所以我们可以写一个函数liftToMaybe来为我们做这件事:

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

我们可以做的进一步观察是,因为函数是值,所以我们也可以有Maybe函数的值。Maybe为了对它们做任何有用的事情,我们可以再次打开它们...直接抽象:

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

其中,使用我们liftToMaybe上面的函数,我们可以简化一点:

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

Reite 的答案中的<$>and<*>运算符基本上只是liftToMaybe(也称为fmap)和的中缀名称maybeApply;他们有类型

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

您现在并不需要知道FunctorandApplicative是什么(尽管您应该在某个时候研究它们;它们基本上是上述Maybe函数对其他类型的“上下文”的概括)-基本上,只需替换fMaybeand您会看到这些功能与我们之前讨论过的功能基本相同。

现在,我把它应用到你原来的乘法问题上(尽管其他答案有点破坏它)。

于 2014-03-08T11:33:15.110 回答
3

你是对的,问题是你试图将两个Maybe值相乘,但(*)只适用于Num.

事实证明,Maybe是类型类的一个实例Applicative。这意味着您可以将使用 typea的函数“提升”为使用 type 的函数Maybe a

import Control.Applicative

提供的两个功能Applicative是:

pure :: a -> f a将纯值置于“中性环境”中。因为Maybe,这是Just

(<*>) :: f (a -> b) -> f a -> f b允许您将“上下文中的函数”应用于两个“上下文中的值”。

所以,假设我们有这个纯粹的计算:

(*) 2 3

以下是Maybe上下文中的一些类似计算:

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

如果函数或参数之一“缺失”,我们返回Nothing.

(<*>)是使用应用程序时的显式应用程序运算符(而不是使用空格,就像我们使用纯值时一样)。

探索(<*>)其他实例的作用是有益的Applicative,例如[]

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]
于 2014-03-08T11:14:34.750 回答