我是haskell,函数式语言和monads的新手。
一个月来,我一直在玩这个游戏;我读过给你一个哈斯克尔,还在玩snap,试图让我的haskell网站。
但是有件事困扰着我:单体的抽象。如果我理解正确的话,monads就是可以排序的“数据容器”。例如,我可以用">>=“来”解压“它,并且会为我做更多的”幕后“工作,这样如果我没有单一的定义,我就必须猜测它是如何被解压的。
例如:
我们有一个列表单,解压它将对它的元素排序。
[1,2,3] >>= return . (+1) -- gives back [2,3,4]
或者像这些例子中的作者那样更复杂的单曲:日志作者Monad
或者我可能有一个webWriter monad,对于它的值的每一个“解压缩”,它都会向某个远程服务器发送一个请求(我不确定这个请求,但我试图给出一个极端的例子)。
我的问题是:仅通过查看单一用户界面(我猜这是类型定义),我可以知道应用函数('>>=‘,'applyLog')在幕后做什么吗?
希望我能解释清楚。
谢谢奥伦。
发布于 2013-09-10 06:13:10
虽然您不能仅仅通过查看接口就知道(>>=)
对特定的monad做了什么,但是为了构成一个“适当的”monad,每个monad都必须遵守一些规则。这限制了return
和(>>=)
的可能实现。monad定律如下:
return a >>= f
等于f a
m >>= return
等于m
(m >>= f) >>= g
等于m >>= (\x -> f x >>= g)
例如,如果List monad的return
被定义为\x -> [x,x]
而不是\x -> [x]
,那就违反了左标识规则。return 5 >>= \x -> [x+1]
将不同于(\x -> [x+1]) 5
。
还有,并不是所有的单元组都可以直观地理解为某种“容器”。。容器类推适用于List,也可能是这样,但是读者呢?“阅读器”值并不真正“包含”任何内容。相反,它是对依赖于外部不变环境的计算的描述。
Monad是实现monad接口并尊重monad规则的任何东西。
编辑:是如何直观地显示单个实例对给定类型所做的操作的一个示例,请考虑流包中的Data.Stream.Infinite.Stream。流就像列表,只是它们永远是无限的。
流具有Monad实例。在这种情况下,return
和(>>=)
会做什么?
return
的类型为a -> Stream a
。这种类型的唯一可能函数是返回作为参数传递的值的无限重复的函数。
(>>=)
更棘手。它有Stream a -> (a -> Stream b) -> Stream b
类型。这种类型的一个可能函数是将第一个参数的头应用到第二个参数,返回结果流的函数。s >>= f = f $ head s
。
(>>=)
的另一个可能的实现是将类型a -> Stream b
的函数应用于原始流的每个元素,得到Stream (Stream b)
类型的中间结果,然后以某种方式将流折叠为单个Stream b
值。怎么做?你可以简单地取无限正方形的对角线!
哪个版本的(>>=)
与单一法规兼容?第一个当然不是,因为它破坏了正确的身份。1,2,3,4... >>= return
的结果是1,1,1,1...
。第二个实现尊重正确的身份(您知道为什么吗?)这给了我们更多的保证,它可能是为流实现(>>=)
的正确方法。当然,你需要所有法律的实际证明才能确定!
发布于 2013-09-10 06:35:58
我描述了四个信息源,您可以使用这些信息源来了解针对特定monads的>>=
行为。
>>=
类型
>>=
的类型总是相同的。它是在Monad
类型类中指定的。见文档。其类型是:
(>>=) :: forall a b. m a -> (a -> m b) -> m b
其中m
是您感兴趣的特定monad的占位符。例如,对于list monad,>>=
的类型是:
(>>=) :: forall a b. [a] -> (a -> [b]) -> [b]
注意,我刚刚用[...]
代替了[...]
。
一元律
>>=
的实现是不同的,但每一个单一的,但所有的单一是应该遵守单一的法律。这些规则在Monad
类型类的文档中指定。见文件再一次。这些法律是:
return a >>= k == k a
m >>= return == m
m >>= (\x -> k x >>= h) == (m >>= k) >>= h
因此,无论具体的monad的实现是什么,您都可以使用这些规则来对代码进行推理。例如,如果您的代码包含类似于法律左侧的代码,则可以用相应的法律右侧替换该代码,并且行为不应改变。
这是一个如何使用单一定律的例子。假设我编写了以下代码:
foo = do
x <- bar
return x
我们甚至不知道这里使用的是什么单数,但是我们知道有一些单数,因为我们看到了do符号。要应用monad定律,我们必须对>>=
的调用使用do符号。
foo = bar >>= (\x -> return x)
注意,\x -> return x
和return
是一样的(通过η-约简)。
foo = bar >>= return
根据第二个monad定律,这个代码的意思与调用bar完全相同。
foo = bar
因此,似乎原始>>=
函数中的foo
根本不能做任何有趣的事情,因为单一法则允许我们忽略它。我们甚至不知道具体的monad为>>=
操作符提供了什么。
特定单曲的文档
如果您需要更多地了解特定monad的>>=
行为,那么特定monad的文档应该会告诉您。您可以使用马蹄搜索文档。例如,StateT
告诉您:
return
函数保持状态不变,而>>=
使用第一次计算的最后状态作为第二次计算的初始状态。
具体单元组的实现
如果您想了解更多关于特定monad的实现的细节,您可能需要查看实际的实现。搜索instance Monad ...
声明。例如,看看StateT
。list monad的实现位于这个文件中的某个地方,搜索instance Monad []
或查看以下内容:
instance Monad [] where
m >>= k = foldr ((++) . k) [] m
m >> k = foldr ((++) . (\ _ -> k)) [] m
return x = [x]
fail _ = []
也许不是最明显的定义,但如果您调用列表单点( list monad )的>>=
,则会发生这种情况。
摘要
所有的monad都共享>>=
和return
的类型签名以及monad律。通过这些约束,每个monad都提供了不同的>>=
和return
实现,如果您想知道所有细节,就必须研究instance Monad ...
声明的源代码。如果您只想学习如何使用特定的monad,请尝试查找有关这方面的一些文档。
发布于 2013-09-10 07:26:27
monad不是“数据容器”。monad是一种高阶计算结构。如果您考虑>>=,可以更好地理解<=<的含义。
f . g
--函数的简单组合
mf <=< mg
--计算的合成。
在我看来,这更能说明问题。但是,<=<可以通过>>=来定义,因此通常只有>>=需要定义。您还可以通过<=<:m >>= f = (f <=< const m) ()
定义<=<:m >>= f = (f <=< const m) ()
"m a“不是数据容器。它只说它实现了"a"-like行为,所以现在我们只能将正确类型的部分组合在一起。(>>=) :: m a -> (a -> m b) -> m b
告诉我们,因为"m“暴露了”-like行为“,所以我们可以加入使用" a”-like行为转换为"b"-like行为的函数。
它如何对任何类型实现"a"-like行为?这就是为什么我们说它是函子:它把任何函数a->b映射到m->m,对于每个函数a->b,它找到(或构建)一个函数,如果f和g组成,那么m和m也组成。这是一个关于保持a和b类型的代数性质的强有力的声明:如果f添加1,而g取掉1,则得到相同的数字;那么,由m组成的m也会使您回到起点--它可能没有存储在任何地方的数字,但是其行为将是“相同的数字”-like。
此外,作为一个单变量,意味着它只关注高阶结构,而不是实际类型:因为"m“可以有任何类型a,这意味着实现不能依赖于类型的具体情况。monad只能使用计算的代数结构--在广义的“代数”中。例如,一个列表,a,可以有任何元素,但是单子只能使用列表的代数结构来操作--枚举元素、拆分列表、折叠等等,但是不能,比方说,把所有元素加起来--加起来就是"a"-like。其他的monad也会有一些特定于monad的功能;如果没有它们,它们可能会非常无用--例如,ask
、atomically
、retry
等等。
https://stackoverflow.com/questions/18710246
复制相似问题