我对类别理论中的monad很熟悉(事实上它们是一个非常简单的概念),但Haskell中的>>=
函数完全令我困惑。好的,所以将值绑定到M a
和函数a -> M u
与首先将monad应用于此函数,然后按指定值对结果进行求值并将结果相乘:a >>= f
与join $ (fmap f) $ a
相同。但这是如何计算的自然描述?有没有一些有用的方法可以帮助我理解它?了解Haskell中的绑定函数
是否有一些不错的文章是而不是面向从C++丛林中新鲜出来的人?
我对类别理论中的monad很熟悉(事实上它们是一个非常简单的概念),但Haskell中的>>=
函数完全令我困惑。好的,所以将值绑定到M a
和函数a -> M u
与首先将monad应用于此函数,然后按指定值对结果进行求值并将结果相乘:a >>= f
与join $ (fmap f) $ a
相同。但这是如何计算的自然描述?有没有一些有用的方法可以帮助我理解它?了解Haskell中的绑定函数
是否有一些不错的文章是而不是面向从C++丛林中新鲜出来的人?
考虑一元函数组合运算符<=<
。这与.
类似,只不过它适用于一元函数。它可以简单地根据>>=
来定义,因此了解其中一个将教育我们另一个。
(<=<) :: (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 b
和v :: m a
问题。这对fmap f v
意味着什么?那么fmap :: (c -> d) -> m c -> m d
,在这种情况下,c = a
和d = m b
,所以fmap f :: m a -> m (m b)
。现在,当然,我们可以将v :: m a
应用于此功能,导致m (m b)
。但是究竟是那么结果类型m (m b)
是什么意思?
innerm
代表从f
产生的背景。 外部m
表示源自v
的上下文(不应打扰此原始上下文)。
然后你join
那m (m b)
,把这两个上下文一起粉碎成m a
。这是Monad
定义的核心:您必须提供一种方法来一起粉碎上下文。您可以查看各种Monad
实例的实现细节,试图了解它们如何“拼合”上下文。然而,这里的要点是,“内在背景”在你将它与“外部背景”合并之前是不可观测的。如果使用v >>= f
,则没有实际功能f
的概念接收纯值a
并产生简单的一元结果m b
。相反,我们知道f
在v
的原始上下文内行为。
如果域名不是一元的,那么'f'如何利用额外的上下文信息? – 2012-02-01 14:53:04
@AlexeiAverchenko,因为它的范围是* monadic。这正是monad的魔力:即使单子函数的输入是* not * monadic,输出*是*。因此,为了影响一元函数a - > m b'的结果,'>> ='是传达一元值'm a'的上下文的一种方法。请注意,对于这两者,'m'必须*相同。例如,[1,2,3] >> = print'是没有意义的,因为[]和IO不一样。 – 2012-02-02 01:14:52
但是函数'a - > m b'没有办法访问'm a'的附加数据,数据只是乘以'join :: m m b - > m b'。 – 2012-02-02 03:49:20
或许=<<
从计算的角度来看更容易理解(它只是flip (>>=)
)。它已输入(=<<) :: (Monad m) => (a -> m b) -> m a -> m b
,并对应于功能应用程序的类型,参见($) :: (a -> b) -> a -> b
。因此>>=
只是在单一级别上翻转的功能应用程序。
此外,在(>>=)
脱糖do
符号被使用,并且do
语法对应非常给命令性代码(在合适的单子)。
或者你可以把'= <<'看作是一个提升函数。它提升一个单子函数'a - > m b'以接受单子输入'm a - > m b'。 – 2012-01-31 08:39:17
嗯。我认为一个好方法是想一下>>=
让你组成计算;计算本身的格式为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建模的“计算”是非确定性的转换函数。
这里是它是如何工作的计算模型的粗略的想法:用Monad
实例的类型构造M
代表的参数数据结构,该结构的非参数部分可以包含其他信息。 return
和join
对应于结构的那些部分的某种monoid。函数a -> M b
基于a
类型的输入引入该结构中的信息。因此,通过解除函数a -> M b
到M a -> M b
,我们使用参数信息M
来创建一些非参数信息,然后将其与已存在的类型为M a
的值中的信息组合。
a -> M b
类型的非对称性给出了非参数信息流的固有方向,而关联性要求意味着总体顺序是唯一重要的事情。
最终的结果是增加了某种具有原因和效果内在概念的上下文的函数。
我认为'>> ='代表了更多monad的“管道”方面,这在实践中很重要,但理论上相当无趣。我发现对monad实现'join'比'> ='更容易实现,我想用join来定义monad会更聪明。 – Landei 2012-01-31 11:51:51
可能是相关的:http://stackoverflow.com/questions/8221395/what-is-a-monad-in-fp-in-categorical-terms – 2012-01-31 23:26:17
@Landei你总是可以定义它为'a >> = f = join (fmap fa)其中join = ...;',假设已经存在的Functor实例。 – 2012-02-02 04:43:34