我试图找出我在Haskell代码中经常看到的IO monad和<-
语法。我已经看到它用于多种数据类型,包括数组和IO。
如果我自己指定一个,那么clojure中的等效操作是什么?
答案 0 :(得分:6)
标记只是标准monad操作的糖。例如,如果你有这样的东西:
do
x <- someMonad
return (someFunction x)
这相当于:
someMonad >>= \x -> return (someFunction x)
因此,使用众多monad库之一的等效Clojure可能是这样的:
(m-bind some-monad (fn [x] (m-result (some-function x))))
答案 1 :(得分:3)
我认为Chuck已回答了你的主要问题,但是如果您想调查使用algo.monads
作为示例在Clojure中实现monad操作的方式,请执行以下操作:
(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"
。
答案 2 :(得分:3)
使用algo.monads
,我们可以轻松定义IO monad(如果不必要的话)。
在Haskell中,IO monad为type IO a = World -> (a, World)
。把它想象成一种行动是很方便的 - 这种行为取决于世界,某种东西,并返回价值和世界。
使用向量而不是元组,这意味着,在Clojure中,IO操作(IO monad的monadic值)看起来像这样:
(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
为该角色创建一个动作,然后通过这个行动的世界。
另一方面,如果我们定义一个monad,我们免费获得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 monad,看起来像这样:
(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)))
最后,我们可以根据这些IO操作编写程序:
(def run-program
(domonad io-m
[line get-line
:let [reversed-line (->> line reverse (apply str))]
_ (put-line reversed-line)]
_))
(run-program :world)