关于传入 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 泄漏的解析器。
相对于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 泄漏的解析器。