关于传入 monad 的 lambda 表达式中的参数,可以证明什么?

What can be proved about parameters in lambda expressions, passed into monads?

相对于fish算子,Monad满足结合性。

(h >=> g) >=> f = h >=> ( g >=> f)

这翻译成绑定(使用 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 代码都将 lambda 的绑定变量分开。例如,

[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch) = 
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

return中的"n"是从top lambda获得的。

更一般地说,

a -> g a >>= \b -> f a b

f 取决于上面的 a 和 b。根据 f、g 和 (>=>) 定义上述内容得到

\a -> (\x -> g a) >=> f a 

我不太明白。它与我很好地展示的上述等式不符。我将 fish 视为这里的基本概念,并且我试图了解它如何与我编写的典型 Monad 交互。我想更好地理解以上内容。

解决这个问题的一种方法是尝试从列表表达式语法中获取含义

[ (n,ch) | n <- [1, 2], ch <- ['a', 'b'] ]

我认为这暗示了一种对称性。

lambda 表达式和 Monad 之间是否存在任何良好的对称性?还是我对此读得太多了?我对高度嵌套的 lambda 表达式的恐惧是错误的还是合理的?

不,没有任何限制。一旦你绑定了一个 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')]

处理您的编辑,您在其中考虑如何编写...

\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) ()

除了提供虚拟 () 的额外技巧外,这就是您在问题中获得的内容。

你的问题的另一个想法:单子是最普遍的,因为效果可能取决于输入。接受输入 a 并产生输出 b 的单子计算 m 可以写成 a -> m b。由于这是一个函数,我们(可以)使用 lambda 定义这样的计算,它自然可以跨越右边。但是这种普遍性使分析计算复杂化,因为您 \a -> g a >>= \b -> f a b.

对于arrows(占据应用函子和monad之间的space)情况有些不同。对于一般箭头,输入必须是显式的——箭头计算 arr 具有 arr a b 的一般类型。因此,在箭头计算中跨越 "forward" 的输入必须使用 Arrow 基元显式​​地线程化。

扩展您的示例

{-# 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 是一个很好的读物,它描述了它们背后的最初动机——构建可以通过静态和动态部分避免 space 泄漏的解析器。