Используя algo.monads
, мы можем легко определить IO-монаду (если нечаянно).
В Haskell IO монада type IO a = World -> (a, World)
. Приятно думать об этом как о действии - что-то, что берет мир, делает что-то, и возвращает значение и мир.
Использование вектора вместо кортежа, это означает, что, в Clojure, действие IO (монадическое значение IO монады) выглядит примерно так:
(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. Этот синтаксический сахар смягчает шаблон для распаковки/упаковки. Нам просто нужно несколько функций: m-result
и m-bind
(m-zero
и m-plus
также удобны, но не нужны).
m-result
(return
в Haskell) принимает значение и оборачивает это как действие:
(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))))])
Теперь, когда мы получили примитивные действия ввода-вывода и средства составляя их, мы можем создавать более интересные. Обратите внимание, что распаковка оператор в Haskell (<-
) подразумевается и результат автоматически обернут m-result
поэтому мы не используем return
о Haskell, чтобы прекратить выражения:
(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)
Заметим, что '<-' (и' do'-обозначения в целом) не является оператором на всех, а синтаксический сахар для '(>> =)' оператор (произносится * привязывать *). Поскольку '(>> =)' - метод класса (класса «Монад»), он имеет совершенно разные реализации в зависимости от базового типа данных. – kosmikus
Как это реализовано для ввода-вывода? – zcaudate
Он не охватывает <- как таковой, но, возможно, этот пост, связанный с функциями Clojure и Haskell, может помочь: http://productivedetour.blogspot.com.es/2013/05/haskell-equivalents-of-some- clojure.html – danidiaz