将值乘以两个“Maybe”monad?

时间:2014-03-08 10:41:36

标签: haskell maybe

我目前正在尝试学习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很新,所以它可能只是一些非常简单的东西,我错过了,或者我把一切都弄错了,但这让我很难过。我做错了什么?

3 个答案:

答案 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的实例。

事实证明,MaybeApplicative类型类的实例。这意味着您可以将使用类型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]