我正在网上阅读一些试图为Scheme程序员提出monad概念的文档。核心思想是,在非命令式编程中,monad用于表示"计算流程,即强制执行表达式的顺序评估。然后它让我感到震惊:因为lambda表达式的主体是按顺序计算的,所以应该理解monad在Scheme中是多余的吗? lambda实体在其他语言(例如Haskell或ML)中的评估方式不同吗?
答案 0 :(得分:8)
因此,表达式的顺序评估不是monad的特征,而是函数的特征。从您编写f (g x)
(Haskell)或(f (g x))
(Scheme)的那一刻起,您就已经g
对f
进行了排序。在Haskell中,我们使用(then f g)
和(bind f (lambda (x) (make-g x)))
之类的运算符来执行此操作,但您可以按照自己喜欢的方式执行此操作。
混淆是:monads 至少在Haskell中带有通用符号,这种通用符号将语句置于命令式顺序中。从Scheme的角度来看,这是一个宏do
,它重写:
(do
(put-string "Hey, what's your name?")
(into x get-line)
(put-string (string-append "Hi, " x "!")))
到
(then
(put-string "Hey, what's your name?")
(bind get-line (lambda (x)
(put-string (string-append "Hi, " x "!")))))
请注意,lambdas得到了平坦的"并用一些新的符号代替"进入"。 (实际上在Haskell中我们说"来自"但它是一个中缀运算符,而不是前缀运算符。)
那么是一个monad,真的吗?好吧,首先,让我们定义他们的词性:monad是形容词,就像形容词" blue"。我可以有一辆蓝色的马车,或一个蓝色的轮子。 Monads是仿函数,这意味着从货车到车轮的功能,我可以为您提供从 blue 货车到 blue 车轮的功能。因此,功能可以在它们下运行"。但是monad有两个特殊的属性。首先,我们有一个只适用于" blue"如果我们提出异常抽象和形而上学的想法:形容词必须能够应用于语言中的任何值。因此,您不仅可以拥有蓝色车厢和蓝色车轮,还可以使用蓝色功能,蓝色程序可以打印出来,#Hello; Hello,World!"到屏幕,蓝色的一切。在给出 blue 蓝色旅行车的意义上,形容词的第二个必须是可凝结,你可以给我一个蓝色旅行车。
这样做的一个形容词是"可以为空的......"。您始终可以获取任何值并根据它生成新的可空值:只需开始接受并检测空值!如果您使用的是静态类型语言,默认情况下这些语言不可为空,那么这很重要。这个形容词是一个仿函数:给定一个函数,我们只需将null转换为null以及我们用函数转换的任何其他东西。它是可以缩小的,因为如果我们有一个可空的可空字符串"可以简化为可以为空的字符串:取" null"和" not-null null"值为" null" " not-null not-null string" to" not-null string"。最后,要将任何字符串表示为可空字符串,请将其转换为" not-null string"。这称为Maybe
monad。它实际上是Either y
monad的一个专门案例,它可以容纳y
。
这样做的另一个形容词是"一个......"的列表。要在形容词下应用函数,请将其应用于列表的所有成员。要将列表列表压缩到列表中,请连接其元素。要从任何x
创建x
的列表,请返回单元素列表。这被称为" list monad"。用这种方式编写就像编写列表推导一样,实际上与Clojure传感器同构,所以你得到了map&过滤器免费。
这样做的另一个形容词是"从s到s的功能和......"。要从任何x
创建其中一个,只需将传入状态与x
配对即可。要应用函数,只需将其应用于输出对的适当侧。十分简单。最后,如果您遇到错综复杂的s -> (s, s -> (s, a))
情况,请通过将传入的s提供给复杂函数来构造s -> (s, a)
,从中获取s并将其提供给s -> (s, a)
出来了。这为您提供了需要返回的(s, a)
。这被称为" State s
monad"因为你可以设想" s"作为一个打字状态。
这样做的另一个形容词是"一个返回"的程序。如果你有一个int,我们可以构造一个什么都不做的程序并返回那个int。如果你有一个程序从int返回一个int和一个函数,我们可以执行该程序,然后将该函数应用于程序的输出。最后,如果你有一个程序返回一个返回int的程序,"只需形成一个程序,运行外部程序来计算内部程序,然后运行内部程序。这称为IO x
monad。 (与承诺类似:承诺的承诺x与承诺的x没有太大差别。)
您可以看到它是一个非常广泛的模式,它并不总是明确涉及"序列"。
一旦您了解Haskell通过将程序作为值并将它们组合在一起来描述其所有I / O操作,您就会意识到基本上我们通过成为宏来执行功能I / O语言即可。宏语言本身不会任何 I / O,但您可以使用它来构建为您执行I / O的程序。您将它们提供给编译器,编译器生成实际的程序,它执行您希望它执行的操作。口译人员会有点冒险,因为口译员需要说"如果我看到一个不是程序的值,我会尝试打印出来;如果我看到一个 程序的值,我将尝试运行该程序,然后打印出来。"这意味着命令提示符处的语法与文件中的语法略有不同。
一旦你理解了Haskell是一个元编程环境,我们正在构建一个程序作为一个值,你将会理解Haskell如何进行功能I / O,你将看到&的可编程语法#34;单子"作为元元编程设计。这种设计比宏(我们在Haskell中也有;它被称为"模板Haskell")复杂得多,但是为很多有用的案例完成了工作。而且它主要是在某种程度上超载了什么" a; b; C; d"意味着依赖于a,b,c和d类型的(共同的)最外层形容词。对于I / O,它意味着"做a,然后做b,然后......&#34 ;;对于状态,它意味着"将由a生成的状态转换为b,然后将b生成的状态转换为c,然后......&#34 ;;对于nullables,它意味着"做一个,如果那不是null,则使用它来做b,如果那不是null,则使用它来做c ..."。对于列表,它是成分列表的笛卡尔积,"在所有方面配对所有内容。"