我目前正在尝试学习Haskell,并且遇到了一个关于Maybe monad的奇怪问题,我似乎无法弄清楚。
作为一项实验,我目前正在尝试取一个字符串,将每个字母转换为任意数字,并将它们相乘/组合在一起。到目前为止,我的情况如下:
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"当我尝试运行它时,它返回以下错误:
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]我不能百分之百确定为什么会发生这个错误,也不确定它到底意味着什么,尽管我怀疑这可能是因为我试图将两个Maybe monads相乘--如果我将test的定义更改为以下内容,程序就会编译并运行良好:
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x | (x, y) <- zip seq $ tail seq, x < y ]我还尝试将类型声明更改为下面的内容,但这也不起作用。
test :: (Num n, Ord n) => [Maybe n] -> [Num (Maybe n)]我真不知道该怎么纠正这个错误。我对Haskell非常陌生,所以这可能只是我错过了一些非常简单的东西,或者我已经把所有的东西都安排得完全错误了,但这是在阻挠我。我做错了什么?
发布于 2014-03-08 11:11:11
可能没有一个num实例,所以不能直接将它们相乘。您需要以某种方式将纯函数应用于上下文中的值。这正是应用函子的用途!
应用函子生活在Control.Applicative中:
import Control.Applicative因此,您有了这个函数,并且希望将它应用于上下文中的2个参数:
(*) :: Num a => a -> a -> a您可能了解了fmap,它接受一个函数并将其应用于上下文中的值。<$>是fmap的别名。当我们对可能值的纯函数fmap时,我们得到以下结果:
(*) <$> Just 5 :: Num a => Maybe (a -> a)所以现在我们有了一个函数,我们需要把它应用到一个值上,这正是应用函子所做的。它的主要运算符是<*>,它具有以下签名:
(<*>) :: f (a -> b) -> f a -> f b当我们专门化它时,我们得到了我们需要的函数:
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b我们应用它,输出就是您所期望的数字。
(*) <$> Just 5 <*> Just 5 :: Num a => Maybe a因此,要使代码编译,您需要更改测试函数以使用<$>和<*>,看看您是否能够找到方法。
发布于 2014-03-08 11:33:15
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,那么我们可以用Just重新包装它,将其转化为Maybe [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 xReite的答案中的<$>和<*>运算符基本上只是liftToMaybe (也称为fmap)和maybeApply的infix名称;它们有以下类型
(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b您现在并不需要知道Functor和Applicative是什么东西(尽管您应该在某个时候研究它们;它们基本上是对其他类型的“上下文”的上述Maybe函数的概括)--基本上,只需用Maybe替换f,您就会发现这些函数基本上都是我们前面讨论过的函数。
现在,我把这个应用到你原来的乘法问题上(尽管其他的答案有点坏了)。
发布于 2014-03-08 11:14:34
您是正确的,问题是您试图将两个Maybe值相乘,但是(*)只在Num的实例中起作用。
事实证明,Maybe是Applicative类型的一个实例。这意味着您可以将使用类型a的功能“提升”到使用类型Maybe a的函数。
import Control.ApplicativeApplicative提供的两个功能是:
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]https://stackoverflow.com/questions/22268226
复制相似问题