编程中的monad是什么,可以解释为:
基本上,我在网上学习时遇到过这个术语。例如,这个Java 8 Stream教程提到了monads(http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/)。所以我想以有用的方式理解基本概念。 (与愚蠢的方式相反,想象一下编程为橙色,那么monad将是葡萄柚......"有点像。)
然而,每个monad教程似乎都需要先前的知识(或试图在运行中教你)Haskell或Haskell语法。
答案 0 :(得分:6)
我将尝试使用Scala语法和语义给出最简单的解释。
Monad是任何具有以下签名的类型:
trait Monad[A] {
def flatMap[B](f: A ⇒ Monad[B]): Monad[B]
}
def unit[A](x: A): Monad[A]
并满足以下法律:
左侧身份:
unit(x).flatMap(f) == f(x)
正确的身份:
m.flatMap(unit _) == m
结合性:
m.flatMap(f).flatMap(g) == m.flatMap(f(_).flatMap(g))
那就是。这就是monad的美丽。满足这些约束的任何都是monad。并且有很多东西可以满足这些限制:列表,集合,词典,地图,选项,数据库查询,延续,错误,副作用,口译员,编译器,......
monad的强大功能来自足够的结构的组合,你可以编写很多操作在monad上的操作,同时这么少的结构很多东西都是单子。这两个属性的组合意味着您可以编写许多一次的操作,然后应用于许多不同类型的对象。
这是monad的问题之一:它们的普遍性。 monad是一种非常通用的抽象,它使得它不是立即显而易见的。我的意思是,集合,列表,数据库查询,副作用,延续和编译器有什么共同之处?
答案 1 :(得分:4)
要把它带到C ++(metaclasses之前,这最终会给出一种正确表达方式),monad的最佳描述是这样的:
参数化类(类似于STL容器类型),即表格类
template <typename a>
class M;
至少支持以下功能:
“琐碎的注射”
template <typename a, typename b>
M<a> pure (a x);
数学家称之为η,Haskell传统上称之为return
,其他语言通常称为unit
。
Functor †映射
template <typename a, typename b>
M<b> fmap (std::function<b(a)> f, M<a> m);
展平操作
template <typename a, typename b>
M<a> join (M< M<a> > mm);
哪位数学家称μ。许多编程语言(包括Haskell)本身并没有实现,而是与fmap
结合使用,因此它被称为flatMap
或>>=
。但join
是最简单的形式。
以满足monad laws。
数组类型示例:
template <typename a>
struct array {
std::vector<a> contents;
};
template <typename a>
array<a> pure(a x) {
return array<a>{{std::vector<a>({x})}}; // array with only a single element.
}
template <typename a, typename b>
array<b> fmap(std::function<b(a)> f, array<a> m) {
std::vector<b> resultv;
for(auto& x: m) {
resultv.push_back(f(x));
}
return array<b>{{resultv}}; // array with the elements transformed by the given function
}
template <typename a>
array<a> join(array< array<a> > mm) {
std::vector<a> resultv;
for(auto& row: mm.contents) {
for(auto& x: row.contents) {
resultv.push_back(x);
}
}
return array<a>{{resultv}}; // array with all the rows concatenated.
}
那么,有什么用呢?好吧,fmap
本身非常有用,当你很快就想在一个数组的所有元素上映射lambda(允许改变类型)时,不必乱用任何迭代器(不像std::transform
})。但是fmap
并不真正需要monad。
真正发挥作用的是一般的排序行动。这个数组示例不会很清楚,所以让我介绍另一个monad:
template <typename a>
struct writer {
std::string logbook;
a result;
};
template <typename a, typename b>
writer<a> pure (a x) {
return writer<a>{{"", x}}; // nothing to log yet
}
template <typename a, typename b>
writer<b> fmap (std::function<b(a)> f, writer<a> m){
return writer<b>{{m.logbook, f(m.result)}};
// simply keep the log as-is
}
template <typename a, typename b>
writer<a> join (writer< writer<a> > mm) {
return writer<a>{{mm.logbook + mm.result.logbook, m.result.result}};
// Concatenate the two logs, and keep the inner value
}
writer
更像是着名的Haskell monad IO
。 writer
类型允许您将任意日志写入功能组合在一起,而不必担心它,收集所有日志信息。
你可能想知道: 在哪里记录?以上操作都没有实际生成任何日志条目!事实上他们没有 - 事实上,如果他们这样做,那么monad法则会受到侵犯! Monads不是特定的行为,预先建立的价值观。相反,他们只是给你一个非常通用的框架,用于“粘合”这样的行为。
这种粘合合成器的一个简单示例是replicateM
,它采用单个monadic动作并按顺序执行 n 次,收集所有结果。不幸的是,这完全没有在C ++中正确输入,但这是一个只适用于编写器monad的专用版本。首先,让我们快速实现前面提到的组合fmap-join,因为它在实践中比join
更方便:
template<typename a, typename b>
writer<b> flatMap(std::function<writer<b>(a)> f, writer<a> xs) {
return join(fmap(f,xs));
}
template <typename a>
writer<array<a>> replicateM (int n, writer<a> m) {
if (n>0) {
writer<array<a>> resultv = fmap(pure, m);
for (int i=1; i<n; ++i) {
resultv = flatMap( [&](array<a> xs){
return fmap( [&](a x){
return xs.push_back(x);}
, m );}
, resultv);
}
} else {
return pure(std::vector<a>());
}
}
请注意,上面的代码都没有实际使用writer
特有的任何内容,因此我可以复制并粘贴它并将其用于任何其他monad。 (或者,使用具有更高亲缘性的多态性的语言,只需一次性编写。)
replicateM
在writer
的情况下做了什么,坦率地说是非常愚蠢的 - 它只是重复相同的日志消息n
次,并复制结果值n
次。但是,这只是最简单的示例:monad还可以具有更多功能,例如,在IO monad中,每次调用都可能产生不同的结果(例如,因为它从标准输入读取)。通用monad接口允许您抽象出各种不同的副作用,但仍然清楚地跟踪在给定上下文中可能发生的副作用。
† 不幸的是,C ++社区滥用单词“functor”只是为了描述功能对象。虽然这是一个相关的概念,但functor实际上不止于此。