我正在尝试理解IO monad以及在Haskell代码中经常看到的<-
语法。我看到它被用于多种数据类型,包括数组和IO。
如果我要自己指定一个等效操作,那么Clojure中的等效操作是什么?
Do-notation只是标准monad操作的语法糖。例如,如果你有类似这样的东西:
do
x <- someMonad
return (someFunction x)
someMonad >>= \x -> return (someFunction x)
因此,使用众多单子库之一的Clojure的等效语法可能如下所示:
(m-bind some-monad (fn [x] (m-result (some-function x))))
我认为 Chuck 已经回答了你的主要问题,但是如果你想探索使用 algo.monads
作为示例在 Clojure 中可以如何实现单子操作的方式,可以参考以下内容:
(domonad state-m
[_ (set-state "foo")
x (fetch-state)]
x)
这与Haskell的相当(好吧,几乎相当,详见下文)
do
_ <- put "foo" -- see below for a comment on this
x <- get
return x
algo.monads
中,<-
消失了,因为实际上它在每一行都被暗示了。_
:在Clojure中,_
实际上并不神奇,它将绑定到由set-state
返回的值,但使用此符号作为不关心的本地名称是惯用法。当然,在Haskell中,通常只需要写put "foo"
而不是_ <- put "foo"
。algo.monads
,我们可以轻松地(尽管没有必要)定义一个IO单子。type IO a = World -> (a, World)
。很方便将其视为一种动作——需要传入世界,执行某些操作,并返回值和世界。(fn [world]
; some stuff
[value world])
get-char
和put-char
。
get-char
是一个动作,它获取输入,读取一个字符,并返回该字符及其值作为输出:(defn read-char
[]
(-> *in* .read char))
(defn get-char
[world]
[(read-char) world])
put-char
函数接受一个字符,创建一个动作,并在给定世界的情况下打印该字符并返回一些(无关紧要的)值:
(defn put-char
[c]
(fn [world]
(print c)
[nil world]))
(put-char \a)
会返回一个动作;((put-char \a) :world)
将调用该动作,打印出a
并返回[nil :world]
。get-char
,解包其字符和世界,在put-char
中创建该字符的动作,然后将世界传递给该动作。domonad
(类似于Haskell的do
)。这种语法糖减轻了解压/打包样板文件。我们只需要一些函数:m-result
和m-bind
(m-zero
和m-plus
也很方便,但不是必需的)。
m-result
(Haskell中的return
)接受一个值并将其封装为一个动作:(fn [v]
(fn [world]
[v world]))
m-bind
(在Haskell中是>>=
)接受一个操作和一个函数,该函数将常规值转换为操作,"解包"该值通过调用操作,并将函数应用于其上。在IO单子中,看起来像这样:
(fn [io f]
(fn [world]
(let [[v new-world] (io world)]
((f v) new-world))))
algo.monads
,我们可以定义io-m
如下:(defmonad io-m
[m-result (fn [v]
(fn [world]
[v world]))
m-bind (fn [io f]
(fn [world]
(let [[v new-world] (io world)]
((f v) new-world))))])
现在我们已经有了原始的IO操作和组合它们的方法,我们可以创建更有趣的操作。请注意,Haskell的解包运算符 (<-
) 是隐式的,结果会自动用 m-result
包装,因此我们不需要使用Haskell的 return
语句来终止表达式:
(declare get-rest-of-line)
(def get-line
(domonad io-m
[c get-char
line (if (= c \newline)
(m-result "")
(get-rest-of-line c))]
line))
(defn get-rest-of-line
[c]
(domonad io-m
[cs get-line]
(str c cs)))
(defn put-line
[s]
(if (seq s)
(domonad io-m
[_ (put-char (first s))
_ (put-line (subs s 1))]
_)
(put-char \newline)))
(def run-program
(domonad io-m
[line get-line
:let [reversed-line (->> line reverse (apply str))]
_ (put-line reversed-line)]
_))
(run-program :world)
<-
(以及一般而言的do
表示法)根本不是运算符,而是(>>=)
运算符(发音为“bind”)的语法糖。由于(>>=)
是Monad
类的一个类方法,因此它具有非常不同的实现,取决于底层数据类型。 - kosmikus<-
不能与“几乎任何东西”一起使用,只能与单子一起使用。 - Matt Fenwick