我正在学习Haskell并制作一些示例。我不知道为什么第二个例子不起作用
foo :: Int -> Int -> Maybe Int
foo 0 0 = Nothing
foo a b = Just $ a + b
bar :: Int -> Maybe Int
bar 0 = Nothing
bar a = Just $ a + 1
-- This works
Just 4 >>= bar
-- Why this doesn't work?
(Just 4 Just 4) >>= foo
-- This works
do
a <- Just 3
b <- Just 4
foo a b
正如注释所说,当(Just 4 Just 4)
只需要一个参数时,它会尝试将构造函数Just
应用于3个参数。所以,我假设您想要类似(Just 4, Just 4)
的东西,并希望它像您的最后一个例子一样工作。
";绑定";运算符为CCD_ 4。这意味着运算符后面的预期函数只接受一个参数,而不是两个参数。所以,再一次,它不起作用的最终原因是,你的函数使用了错误数量的参数。(部分应用程序意味着您不需要同时提供所有参数,但听起来您希望其他数据能够神奇地路由到丢失的参数…)
将您的do
示例取消粘贴到>>=
表单,翻译为:
Just 3 >>= a -> Just 4 >>= b -> foo a b
为了更清楚一点,我将在括号中加上lambdas:
Just 3 >>= ( a -> Just 4 >>= (b -> foo a b) )
这样可以更容易地看到,您可以简化内部lambda:
Just 3 >>= ( a -> Just 4 >>= foo a )
因此,将丢失的数据路由到额外的参数是可能的!但是,你必须自己制定路线。。。
Haskell函数没有什么特别神奇的地方;与动态语言相比,它们在如何调用方面往往更为挑剔。最大的";魔术;这里的类型检查器经常可以判断你什么时候没有正确使用它们。
(正如其他答案所指出的)>>=
并没有什么神奇之处——它只是另一个函数,为了了解如何使用它,你需要看看它的类型。
它不起作用,因为>>=
是一个完全正规的运算符(而运算符是完全正规的函数)。
您似乎认为>>=
是一种特殊的语法,用于从其左侧的一元值中获取值,并将其提供给右侧的函数。它不是特殊的语法;相反,>>=
本身是一个应用于其左侧和右侧值的函数(然后根据您的期望计算结果)。
然而,这意味着左参数和右参数必须是可以作为普通值存在的事物的有效表达式;您可以简单地使用var = <expr>
语法绑定到变量。Just 4 >>= bar
起作用是因为(除其他要求外)Just 4
本身是Maybe Int
类型的有效表达式,而bar
是Int -> Maybe Int
类型的有效表达。Just 4 Just 4 >>= foo
不起作用,因为Just 4 Just 4
是而不是正确的表达式(它的类型是什么?);它被解释为将Just
应用于3个独立的参数4
、Just
和4
,而您希望它是两个独立的值Just 4
和Just 4
。但是,即使您可以让编译器将其中的某些内容解释为两个单独的值,>>=
也不可能被传递两个单独值作为其左参数;它期望(在这种用法中)CCD_ 26类型的单个值。
如果你有一个像foo
这样的函数,它需要两个参数,并且你想从一元上下文中的值中获取这些参数,那么你不能只应用>>=
,你需要编写这样的代码(就像你最后一个使用do
块的例子一样;还有很多其他方法可以做等效的事情)。
其他答案描述了为什么这不起作用。但是IMO,你想要这个是很合理的,事实上Just 3 >>= x -> Just 4 >>= y -> foo x y
是一个有点愚蠢的任务解决方案。基本上,x
和y
的值是相互独立的,但您是按顺序获取它们的,因此完整的y
计算原则上可能取决于x
的值。
Monad在这里并不是真正正确的抽象概念,它们太强大了。要非顺序地获取x
和y
,可以使用Applicative
接口。现在大多数哈斯克尔人更喜欢的形式(我认为)是
foo <$> Just 3 <*> Just 4
您可以将其理解为"将有效值Just 3
和Just 4
压缩到具有两个值的单个操作中,然后将(>>=) :: Monad m => m a -> (m a -> b) -> m b
0应用于这些值"。
。。。事实上,这并不是它真正的工作方式,对我来说,当我第一次了解应用程序时,这非常令人困惑。也就是说,上面的表达式实际上被解析为
(foo <$> Just 3) <*> Just 4
它看起来又像是顺序样式。但事实并非如此,这里所发生的只是一个通过应用性值传递多个值而不必将它们分组到合适的元组的咖喱/懒惰技巧。真正像我解释的那样工作的代码是
uncurry foo <$> ((,)<$>Just 3<*>Just 4)
这里,(,)<$>Just 3<*>Just 4
的求值结果为Just (3,4)
。然后,需要以未携带的形式将foo
映射到上面,因此这两个参数被接受为元组。它结构清晰,但很尴尬,因为我们反对哈斯克尔的咖喱风格。
(从数学上讲,这个元组是概念上发生的:一般来说,你在单胚范畴中工作。应用函子的其他一些化身有这样一个元组组合子作为它们的底层接口,而不是<*>
;例如invertible
包中的>*<
。)
foo<$>Just 3<*>Just 4
的诀窍在于,我们不构建元组,而是从将foo
部分应用于3
结果开始。这实际上还不需要任何应用性/单元性的东西——我们基本上只是将包含的值——通常是:values——从3
转换为foo 3
,而不涉及它们的上下文。你可以认为这纯粹是一种象征性的操作。请注意,此时的类型为Maybe (Int -> Int)
。
然后使用<*>
组合子将两个Maybe
上下文压缩在一起,并同时将foo 3
部分求值函数应用于其第二个参数。
我个人喜欢这种形式,它也相当于:
liftA2 foo (Just 3) (Just 4)
我们还没有完成:上面所有的建议都给出了Maybe (Maybe Int)
类型的结果。要将其扁平化为Maybe Int
,实际上需要monad接口。一种选择是
join $ foo <$> Just 3 <*> Just 4