什么是Haskell的< - ?等效的clojure

时间:2013-12-06 19:25:05

标签: haskell clojure monads

我试图找出我在Haskell代码中经常看到的IO monad和<-语法。我已经看到它用于多种数据类型,包括数组和IO。

如果我自己指定一个,那么clojure中的等效操作是什么?

3 个答案:

答案 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-charput-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-resultm-bindm-zerom-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)