我正在学习monad并且有一些问题。
这就是我现在所处的位置。请纠正我错在哪里。
>>=
符号是中缀运算符。中缀运算符是带有两个参数(左侧和右侧)并返回值的函数。
>>=
符号称为绑定运算符,并具有签名Monad m => m t -> (t -> m u) -> m u
。但是,这些类型似乎没有排在这里。我们得到类型为m t
的值,第二个参数是一个带t
的函数。 (我不知道如何连接点。)
这必须意味着绑定函数能以某种方式从m
中移除m t
以获取t
并将其传递给函数。
以下是我的问题:
能否从m
移除m t
只能在此类绑定操作符中执行的操作。这个绑定运算符是否有一些特殊的特权?
与状态变化有什么关系?我理解(我认为)monad的目标是“包装”副作用,以便它们与程序的其余部分隔离开来。但是绑定运算符在这个中的作用是什么?
答案 0 :(得分:12)
是能够从'M t'移除'M'的能力,只有在这样的绑定操作符中才有可能。
嗯,它当然可以在bind运算符中,因为它的类型指定:
(>>=) :: m a -> (a -> m b) -> m b
monad的'run'函数通常也可以这样做(从计算中返回纯值)。
monads的目标是“包装”副作用,以便它们与程序的其余部分隔离
嗯。不,monads让我们模拟计算的概念。副作用计算只是一个这样的概念,如状态,回溯,延续,并发,交易,可选结果,随机结果,可恢复状态,非确定性......所有这些can be described as a monad
我认为IO monad是你所指的。这是一个有点奇怪的单子 - 它会生成对世界状态的抽象变化序列,然后由运行时进行评估。绑定只是让我们按照IO monad中的正确顺序对事物进行排序 - 然后编译器将所有这些排序的世界修改操作转换为更改该机器状态的命令性代码。
这对IO monad来说非常具体,一般不是monad。
答案 1 :(得分:10)
能否从'M t'移除'M'只能在这样的绑定操作符中执行的操作。这个绑定运算符是否有某些特殊的特权?
Bind绝不是一种特殊情况,但通常它将与monads数据类型在同一模块中定义。因此,它可能知道(和使用)模块未导出的详细信息。通常的情况是模块导出数据类型,但不是它的构造函数或关于类型内部结构的其他细节。然后,对于使用该模块的代码,数据类型的内部工作是不可见的,并且该代码不能直接修改此类型的值。
反对在模块内定义的函数,例如一些绑定运算符>>=
,可以从它们定义的模块中访问它们所喜欢的任何东西。所以这样的函数可能能够做“外部”函数做不到。
特殊情况是IO
monad,因为它不是由模块定义的,而是内置于运行时系统/编译器中。这里编译器知道它的实现的内部细节,并公开像IO
的{{1}}这样的函数。这些函数的实现确实是特权的,因为它们生活在“程序之外”,但这是一个特例,并且这个事实不应该在Haskell中观察到。
与状态变化有什么关系?我理解(我认为)monad的目标是“包装”副作用,以便它们与程序的其余部分隔离开来。但是这个绑定运算符的作用是什么?
它实际上并不需要与状态更改有关,这只是一个可以用moand处理的问题。 >>=
monad用于按特定顺序执行IO,但通常monad只是将函数组合在一起的方法。
一般来说,monad(特别是它的绑定函数)定义了一种方法,在这种方式中,某些函数应该组合成更大的函数。这种组合函数的方法在monad中被抽象出来。这种组合的工作原理或者为什么要以这种方式组合函数并不重要,monad只是指定了以某种方式组合某些函数的方法。 (另请参阅this "Monads for C# programmers" answer,其中我基本上重复了几次示例。)
答案 2 :(得分:5)
以下是类型类Monad
的定义。
class Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
m >> k = m >>= \_ -> k
fail s = error s
类型类Monad
的每个类型实例都定义了自己的>>=
函数。以下是类型实例Maybe
:
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(Just _) >> k = k
Nothing >> _ = Nothing
return = Just
fail _ = Nothing
我们可以看到,因为Maybe
>>=
版本的Maybe
是专门定义的,用于理解data Maybe a
类型实例,并且因为它是在具有合法访问权限的地方定义的Nothing
数据构造函数Just a
和Maybe
>>=
a
版本的Maybe a
能够展开x :: Maybe Integer
x = do a <- Just 5
b <- Just (a + 1)
return b
x :: Maybe Integer
x = Just 5 >>= \a ->
Just (a + 1) >>= \b ->
Just b
并传递它们。
通过一个例子,我们可以采取:
= (\a ->
Just (a + 1) >>= \b ->
Just b) 5
= Just (5 + 1) >>= \b ->
Just b
= (\b ->
Just b) (5 + 1)
= Just (5 + 1)
= Just 6
脱糖,记号变为:
{{1}}
评估为:
{{1}}
答案 3 :(得分:4)
类型确实排队,有趣的是。这是怎么回事。
请记住,monad也是一个仿函数。为所有仿函数定义了以下函数:
fmap :: (Functor f) => (a -> b) -> f a -> f b
现在的问题是:这些类型真的排队吗?嗯,是。给定a
到b
的函数,如果我们有一个f
可用的环境a
,我们就有一个f
的环境b
1}}可用。
类比三段论:
(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal
现在,如你所知,monad是一个配有bind和return的仿函数:
return :: (Monad m) => a -> m a
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b
你可能不等同,它是一个配有回归和加入的仿函数:
join :: (Monad m) => m (m a) -> m a
了解我们如何剥离m
。使用monad m
,您无法始终从m a
到a
,但始终可以从m (m a)
到m a
。
现在看(=<<)
的第一个参数。它是(a -> m b)
类型的函数。将该函数传递给fmap
时会发生什么?你得到m a -> m (m b)
。因此,使用函数m a
对a -> m b
进行“映射”可为您提供m (m b)
。请注意,这与join
的参数类型完全相同。这不是巧合。 “绑定”的合理实现如下:
(>>=) :: m a -> (a -> m b) -> m b
x >>= f = join (fmap f x)
实际上,绑定和连接可以相互定义:
join = (>>= id)
答案 4 :(得分:2)
我非常推荐您阅读(http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html)。它给出了monad存在的完美,常识的原因。
答案 5 :(得分:2)
我理解(我认为)monad的目标是“包装”副作用,以便它们与程序的其余部分隔离开来。
它实际上比这更微妙。 Monads允许我们以非常通用的方式对排序进行建模。通常当你与领域专家交谈时,你会发现他们说的是“首先我们尝试X.然后我们尝试Y,如果这不起作用,那么我们尝试Z”。当你用传统语言实现类似的东西时,你会发现它不合适,所以你必须编写大量额外的代码来覆盖领域专家所说的“then”这个词。
在Haskell中,您可以将其实现为monad,并将“then”转换为bind运算符。因此,例如我曾经编写过一个程序,其中必须根据某些规则从池中分配项目。对于案例1,你从游泳池X拿走它。如果那是空的那么你就转移到游泳池Y.对于案例2你必须从游泳池Y直接拿它。等等十几个案例,包括你带的地方最近最少使用的是游泳池X或Y.我专门为这份工作写了一个自定义monad,以便我可以写:
case c of
1: do {try poolX; try poolY}
2: try poolY
3: try $ lru [poolX, poolY]
效果很好。
当然,这包括传统的测序模型。 IO monad是所有其他编程语言都具有的模型;它只是在Haskell中它是一个明确的选择而不是环境的一部分。 ST monad为您提供IO的内存变异,但没有实际的输入和输出。另一方面,State monad允许您将状态限制为命名类型的单个值。
对于真正弯曲大脑的东西,请参阅this blog post关于落后状态monad的内容。国家以与“执行”相反的方向传播。如果你认为这就像一个状态monad执行一个指令然后执行下一个指令,那么“put”会将状态值及时向后发送到任何前面的“get”。 实际发生的是一个相互递归的函数设置,只有在没有悖论的情况下才会终止。我不太确定在哪里使用这样的monad,但它说明了monad是计算模型的观点。
如果你还没准备好,那么只需将bind视为一个可重载的分号。这让你有很长的路要走。