什么可以证明lambda表达式中的参数,传递到monad?

时间:2017-01-06 22:55:23

标签: haskell monads

相对于鱼类操作者,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表达式的恐惧是错误还是合理?

3 个答案:

答案 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,它描述了它们背后的原始动机 - 构建可以通过静态和动态部分避免空间泄漏的解析器。