简体   繁体   中英

What is the clojure equivalent for Haskell's <-?

I'm trying to figure out the IO monad and the <- syntax which I often see in Haskell code. I've seen it used with multiple datatypes, including arrays and IO.

What is the equivalent operation in clojure if I were to specify one myself?

Do-notation is just sugar for the standard monad operations. For example, if you have something like this:

do
  x <- someMonad
  return (someFunction x)

That's equivalent to this:

someMonad >>= \x -> return (someFunction x)

So the equivalent Clojure using one of the many monad libraries might be something like this:

(m-bind some-monad (fn [x] (m-result (some-function x))))

I think Chuck has answered your main question, but in case you'd like to investigate the way the monad operations can be implemented in Clojure using algo.monads as an example, the following:

(domonad state-m
  [_ (set-state "foo")
   x (fetch-state)]
  x)

is equivalent (well, almost, see below) to Haskell's

do
  _ <- put "foo" -- see below for a comment on this
  x <- get
  return x

In algo.monads the <- disappears, because effectively it is implied on every line.

About the "almost" and the _ above: _ actually isn't magic in Clojure and it will be bound to the value returned by set-state , but it's idiomatic to use this symbol as the name of locals one doesn't care about. Of course in Haskell it would be more usual simply to write put "foo" instead of _ <- put "foo" .

Using algo.monads , we can define an IO monad easily (if unnecesarrily).

In Haskell, the IO monad is type IO a = World -> (a, World) . It's handy to think of this as an action - something which takes the world, does something , and returns a value and the world.

Using a vector instead of a tuple, this means that, in Clojure, an IO action (a monadic value of the IO monad) looks something like this:

(fn [world]
    ; some stuff
    [value world])

To do something interesting, we need a couple of actions: get-char and put-char .

get-char is an action which takes in the world, reads a char, and returns that char as its value alongside the world:

(defn read-char
  []
  (-> *in* .read char))

(defn get-char
  [world]
  [(read-char) world])

put-char takes a character and creates an action which, given a world, prints the character and returns some (inconsequential) value:

(defn put-char
  [c]
  (fn [world]
    (print c)
    [nil world]))

Note that, to make an action happen, we have to supply a world. For instance, (put-char \\a) will return an action; ((put-char \\a) :world) will invoke that action, printing a and returning [nil :world] .

Composing these actions is potentially a very messy process. If, for example, you wanted to get a character, then print it, you'd have to call get-char , unpack its character and world, create an action for that character with put-char , then pass the world to that action.

On the other hand, if we define a monad, we get domonad (the equivalent to Haskell's do ) for free. This syntactic sugar alleviates the unpacking/packing boilerplate. We just need a few functions: m-result and m-bind ( m-zero and m-plus are also handy, but not necessary).

m-result ( return in Haskell) takes a value and wraps it up as an action:

(fn [v]
  (fn [world]
    [v world]))

m-bind ( >>= in Haskell) takes an action and a function which takes a regular value to produce an action, "unwraps" the value by invoking the action, and applies the function to it. With the IO monad, that looks like this:

(fn [io f]
  (fn [world]
    (let [[v new-world] (io world)]
      ((f v) new-world))))

So, using algo.monads , we can define io-m as follows:

(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))))])

Now that we've got the primitive IO actions and a means of composing them, we can create more interesting ones. Note that Haskell's unpacking operator ( <- ) is implicit and the result is automatically wrapped with m-result so we don't use Haskell's return statement to terminate the expressions:

(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)))

Finally, we can write a program in terms of these IO actions:

(def run-program
  (domonad io-m
    [line get-line
     :let [reversed-line (->> line reverse (apply str))]
     _ (put-line reversed-line)]
    _))

(run-program :world)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM