相对于鱼类操作者,Monads满足相关性。
(h >=> g) >=> f = h >=> ( g >=> f)
这转换为bind(与lambda表达式)相似,
\a -> h a >>=(\b -> g b >>= \c -> f c) =
\a ->(h a >>= \b -> g b)>>= \c -> f c
这意味着以下等式是明确的
( a -> h a >>= \b -> g b >>= \c -> f c ) = h >=> g >=> f
这是理解Monadic构图的好方法。
然而,并非所有Monadic代码都将绑定变量保持为lambdas分开。例如,
[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch) =
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
" n"在回报中是从顶级lambda获得的。
更一般地说,
a -> g a >>= \b -> f a b
f取决于上面的a和b。用f,g和(> =>)定义上述内容给出
\a -> (\x -> g a) >=> f a
我不太了解。它与我上面展示的方程式不相符。我认为鱼是这里的基本概念,我试图理解它与我写的典型Monads的相互作用。我想更好地理解上述内容。
解决此问题的一种方法是尝试从列表表达式语法
中获取含义[ (n,ch) | n <- [1, 2], ch <- ['a', 'b'] ]
我认为这意味着一种对称性。
连接lambda表达式和Monads有没有很好的对称性?还是我读了太多这个?我对高度嵌套的lambda表达式的恐惧是错误还是合理?
答案 0 :(得分:5)
不,没有任何限制。一旦绑定了lambda,就可以anything。这是Applicative
更喜欢Monad
的原因之一,因为它更弱(从而为您提供更强的自由定理限制)。
( [1,2] >>= \n -> "ab" >>= \ch -> return (n,ch) )
≡ (,) <$> [1,2] <*> "ab"
≡ liftA2 (,) [1,2] "ab"
≈ liftA2 (flip (,)) "ab" [1,2]
最后一个实际上不是一个合适的等式;仅适用法律保证值对于这些表达式是相同的 参见注释,但结构可以并且会有所不同。
Prelude Control.Applicative> liftA2 (,) [1,2] "ab"
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
Prelude Control.Applicative> liftA2 (flip (,)) "ab" [1,2]
[(1,'a'),(2,'a'),(1,'b'),(2,'b')]
答案 1 :(得分:2)
解决您的编辑问题,您可以在其中考虑如何编写...
\a -> g a >>= \b -> f a b
...使用(>=>)
,在这种情况下实际上没有任何东西丢失。退后一步并准确考虑(>=>)
如何转换为(>>=)
,反之亦然:
f >=> g = \x -> f x >>= g
m >>= f = (const m >=> f) () -- const x = \_ -> x
在第二个等式中,即与您的关注点相关的等式,我们将第一个参数转换为(>>=)
为一个函数,该函数可以使用(>=>)
传递给const
。由于const m >=> f
是一个忽略其参数的函数,我们只是将()
作为伪参数传递,以便恢复(>>=)
。
既然如此,你的例子可以用第二个等式重写:
\a -> g a >>= \b -> f a b
\a -> (const (g a) >=> \b -> f a b) ()
\a -> (const (g a) >=> f a) ()
除了提供虚拟()
的附加技巧之外,您在问题中获得的是什么。
答案 2 :(得分:2)
您的问题的另一个想法:Monad是最常见的,因为效果可能取决于输入。获取输入m
并生成输出a
的monadic计算b
可写为a -> m b
。由于这是一个函数,我们(可以)使用lambdas定义这样的计算,这自然可以跨越右边。但这种普遍性使分析计算变得复杂,因为\a -> g a >>= \b -> f a b
。
对于arrows(占据应用函子和monad之间的空间),情况有所不同。对于常规箭头,输入必须是显式的 - 箭头计算arr
的常规类型为arr a b
。所以一个跨越&#34;前进&#34;在箭头计算必须使用箭头原语显式线程化。
扩展您的示例
{-# LANGUAGE Arrows #-}
import Control.Arrow
bind2 :: (Monad m) => (a -> m b) -> (a -> b -> m c) -> a -> m c
bind2 g f = \a -> g a >>= \b -> f a b
到箭头:函数f
现在必须将一对作为输入(因为箭头被定义为接受一个输入值)。使用箭头do
表示法,我们可以将其表示为
bind2A :: (Arrow arr) => arr a b -> arr (a, b) c -> arr a c
bind2A g f = proc a -> do
b <- g -< a
c <- f -< (a, b)
returnA -< c
甚至更简单地使用Arrow
原语:
bind2A' :: (Arrow arr) => arr a b -> arr (a, b) c -> arr a c
bind2A' g f = (returnA &&& g) >>> f
图形:
--------------->[ ]
\ [ f ]---->
\-->[ g ]-->[ ]
不那么通用,箭头允许在电路实际执行之前推断出有关电路的更多信息。一个很好的阅读是Understanding arrows,它描述了它们背后的原始动机 - 构建可以通过静态和动态部分避免空间泄漏的解析器。