我目前正在尝试学习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]
我不是100%确定为什么会发生这个错误,或者它究竟意味着什么,尽管我怀疑这可能是因为我试图将两个Maybe
monad相乘 - 如果我更改了定义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很新,所以它可能只是一些非常简单的东西,我错过了,或者我把一切都弄错了,但这让我很难过。我做错了什么?
答案 0 :(得分:10)
也许没有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
因此,为了使您的代码编译,您需要更改测试函数以使用<$>
和<*>
,看看您是否可以弄清楚如何。
答案 1 :(得分:5)
Reite的答案是正确的,而且通常我通常建议如何处理它 - 但是,在我看来,你不太明白如何使用Maybe
值;如果是这样的话,现在看看应用仿函数几乎没有意义。
Maybe
的定义基本上只是
data Maybe a = Nothing | Just a
这基本上意味着当您用简单的英语阅读它时的确切含义“类型Maybe a
的值是该类型的值Nothing
,或者{{1}的值}}”。
现在您可以使用模式匹配来处理它,以列表为例:
Just a
这基本上意味着“如果值为 maybeReverse :: Maybe [a] -> Maybe [a]
maybeReverse Nothing = Nothing
maybeReverse (Just xs) = Just $ reverse xs
,则无法撤消,因此结果再次为Nothing
。如果值为Nothing
,那么我们可以{{1}并再次使用Just xs
将其换行以将其变为reverse 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
Reite的答案中的 maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply (Just f) x = liftToMaybe f x
和<$>
运算符基本上只是<*>
(也称为liftToMaybe
)和fmap
的中缀名称;他们有类型
maybeApply
你真的不需要知道现在(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b
和Functor
的内容是什么(尽管你应该在某些时候对它们进行研究;它们基本上是对上述{{1}的概括其他类型的“上下文”的函数 - 基本上,只需用Applicative
替换Maybe
,你就会发现它们基本上和我们之前谈过的函数基本相同。
现在,我把这个应用到你原来的乘法问题上(虽然其他答案有点破坏它)。
答案 2 :(得分:3)
您是对的,问题是您尝试将两个Maybe
值相乘,但(*)
仅适用于Num
的实例。
事实证明,Maybe
是Applicative
类型类的实例。这意味着您可以将使用类型a
的函数“提升”到使用类型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
。
(<*>)
是使用applicatives时的显式应用程序运算符(而不是使用空格,就像我们使用纯值时一样)。
探索(<*>)
对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]