2012-01-31 64 views
10

我对类别理论中的monad很熟悉(事实上它们是一个非常简单的概念),但Haskell中的>>=函数完全令我困惑。好的,所以将值绑定到M a和函数a -> M u与首先将monad应用于此函数,然后按指定值对结果进行求值并将结果相乘:a >>= fjoin $ (fmap f) $ a相同。但这是如何计算的自然描述?有没有一些有用的方法可以帮助我理解它?了解Haskell中的绑定函数

是否有一些不错的文章是而不是面向从C++丛林中新鲜出来的人?

+1

我认为'>> ='代表了更多monad的“管道”方面,这在实践中很重要,但理论上相当无趣。我发现对monad实现'join'比'> ='更容易实现,我想用join来定义monad会更聪明。 – Landei 2012-01-31 11:51:51

+1

可能是相关的:http://stackoverflow.com/questions/8221395/what-is-a-monad-in-fp-in-categorical-terms – 2012-01-31 23:26:17

+1

@Landei你总是可以定义它为'a >> = f = join (fmap fa)其中join = ...;',假设已经存在的Functor实例。 – 2012-02-02 04:43:34

回答

8

考虑一元函数组合运算符<=<。这与.类似,只不过它适用于一元函数。它可以简单地根据>>=来定义,因此了解其中一个将教育我们另一个。

(<=<) :: (a -> m b) -> (b -> m c) -> a -> m c 
(f <=< g) x = g x >>= f 

(.) :: (a -> b) -> (b -> c) -> a -> c 
(f . g) x = g x |> f 
    where z |> h = h z 

.的情况下,被g“执行”第一,然后f上的g输出来执行。在<=<的情况下,g及其效果首先被“执行”,然后执行f及其效果。实际上,由于并非所有的monad都以这种方式工作,所以说在另一个之前“发生”是错误的。

也许更准确地说,f可以利用g提供的其他上下文信息。但这是不完全正确,因为g可能带走上下文信息。如果你想100%正确描述单子,你真的必须走在蛋壳上。

但在几乎所有的非平凡的情况下,f <=< g意味着,一元函数g影响(还有结果)也将随之影响到一元函数f的行为。


为了解决有关​​

考虑f :: a -> m bv :: m a问题。这对fmap f v意味着什么?那么fmap :: (c -> d) -> m c -> m d,在这种情况下,c = ad = m b,所以fmap f :: m a -> m (m b)。现在,当然,我们可以将v :: m a应用于此功能,导致m (m b)。但是究竟是那么结果类型m (m b)是什么意思?

innerm代表从f产生的背景。 外部m表示源自v的上下文(不应打扰此原始上下文)。

然后你joinm (m b),把这两个上下文一起粉碎成m a。这是Monad定义的核心:您必须提供一种方法来一起粉碎上下文。您可以查看各种Monad实例的实现细节,试图了解它们如何“拼合”上下文。然而,这里的要点是,“内在背景”在你将它与“外部背景”合并之前是不可观测的。如果使用v >>= f,则没有实际功能f的概念接收纯值a并产生简单的一元结果m b。相反,我们知道fv的原始上下文内行为

+0

如果域名不是一元的,那么'f'如何利用额外的上下文信息? – 2012-02-01 14:53:04

+0

@AlexeiAverchenko,因为它的范围是* monadic。这正是monad的魔力:即使单子函数的输入是* not * monadic,输出*是*。因此,为了影响一元函数a - > m b'的结果,'>> ='是传达一元值'm a'的上下文的一种方法。请注意,对于这两者,'m'必须*相同。例如,[1,2,3] >> = print'是没有意义的,因为[]和IO不一样。 – 2012-02-02 01:14:52

+0

但是函数'a - > m b'没有办法访问'm a'的附加数据,数据只是乘以'join :: m m b - > m b'。 – 2012-02-02 03:49:20

3

或许=<<从计算的角度来看更容易理解(它只是flip (>>=))。它已输入(=<<) :: (Monad m) => (a -> m b) -> m a -> m b,并对应于功能应用程序的类型,参见($) :: (a -> b) -> a -> b。因此>>=只是在单一级别上翻转的功能应用程序。

此外,在(>>=)脱糖do符号被使用,并且do语法对应非常给命令性代码(在合适的单子)。

+0

或者你可以把'= <<'看作是一个提升函数。它提升一个单子函数'a - > m b'以接受单子输入'm a - > m b'。 – 2012-01-31 08:39:17

6

嗯。我认为一个好方法是想一下>>=让你组成计算;计算本身的格式为a -> m b。所以m b只是表示计算的结果

所以一个计算只需要一些值并产生一些结果。一个很好的例子是列表类型:a -> [b]代表一个非确定性计算。它需要一个输入,但可以产生多个结果。作为计算本身,a -> [b]是有意义的。但你会如何结合这些?自然的答案是你会在前面的结果的全部上执行每个连续的“计算”。这正是>>=为列表所做的。

有一件事确实帮助我看到了这个的实际价值,它考虑了DFA和NFA。你可以想像平凡写DFA在Haskell是这样的:

data State = S1 | S2 | S3 | S4 | Q 
data Input = A | B 
transition :: State -> Input -> State 
transition S1 A = S2 
transition S1 B = S3 
-- and so on... 

然后,我们可以只折叠输入:

foldl transition S1 [A, A, B, B] 

现在,如何将我们利用这个代码,并将其推广到NFA的?那么,NFA的转换“功能”可以被认为是一种非确定性计算。所以我们定义是这样的:

transition S1 A = [S1, S2] 
transition S1 B = [] 

但现在我们不得不做一些奇怪的体操使用foldl!令人高兴的是,我们可以代替foldM。所以这里由monad建模的“计算”是非确定性的转换函数。

3

这里是它是如何工作的计算模型的粗略的想法:用Monad实例的类型构造M代表的参数数据结构,该结构的非参数部分可以包含其他信息。 returnjoin对应于结构的那些部分的某种monoid。函数a -> M b基于a类型的输入引入该结构中的信息。因此,通过解除函数a -> M bM a -> M b,我们使用参数信息M来创建一些非参数信息,然后将其与已存在的类型为M a的值中的信息组合。

a -> M b类型的非对称性给出了非参数信息流的固有方向,而关联性要求意味着总体顺序是唯一重要的事情。

最终的结果是增加了某种具有原因和效果内在概念的上下文的函数。