diff options
author | Konrad Hinsen <konrad.hinsen@laposte.net> | 2009-06-24 16:49:50 +0200 |
---|---|---|
committer | Konrad Hinsen <konrad.hinsen@laposte.net> | 2009-06-24 16:49:50 +0200 |
commit | f9b111c67752220c9d45a7d6ef22c6eecf400c87 (patch) | |
tree | ec13872717ca70f8827a213c6f0a492a5a90f837 /src | |
parent | 6823c51380b69ec12f641fbef09d395237931e40 (diff) |
New library monadic-io-streams
Provides a monadic interface to I/O streams created through
clojure.contrib.duck-streams. The monadic interface makes
it possible to write purely function I/O routines
and run them on I/O streams without any possibility of
aliasing.
Diffstat (limited to 'src')
-rw-r--r-- | src/clojure/contrib/monadic_io_streams.clj | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/clojure/contrib/monadic_io_streams.clj b/src/clojure/contrib/monadic_io_streams.clj new file mode 100644 index 00000000..28772283 --- /dev/null +++ b/src/clojure/contrib/monadic_io_streams.clj @@ -0,0 +1,145 @@ +;; Monadic I/O + +;; by Konrad Hinsen +;; last updated June 24, 2009 + +;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use +;; and distribution terms for this software are covered by the Eclipse +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this +;; distribution. By using this software in any fashion, you are +;; agreeing to be bound by the terms of this license. You must not +;; remove this notice, or any other, from this software. + +(ns + #^{:author "Konrad Hinsen" + :doc "Monadic I/O with Java input/output streams + Defines monadic I/O statements to be used in a state monad + with an input or output stream as the state. The macro + monadic-io creates a stream, runs a monadic I/O statement + on it, and closes the stream. This structure permits the + definition of purely functional compound I/O statements + which are applied to streams that can never escape from the + monadic statement sequence."} + clojure.contrib.monadic-io-streams + (:refer-clojure :exclude (read-line print println flush)) + (:use [clojure.contrib.monads + :only (with-monad domonad state-m state-m-until)]) + (:use [clojure.contrib.generic.functor :only (fmap)]) + (:use [clojure.contrib.duck-streams :only (reader writer)])) + +; +; Wrap the state into a closure to make sure that "evil" code +; can't obtain the stream using fetch-state and manipulate it. +; +(let [key (Object.) + lock (fn [state] (fn [x] (if (identical? x key) state nil))) + unlock (fn [state] (state key))] + + ; + ; Basic stream I/O statements as provided by Java + ; + (defn read-char + "Read a single character" + [] + (fn [s] [(.read (unlock s)) s])) + + (defn read-line + "Read a single line" + [] + (fn [s] [(.readLine (unlock s)) s])) + + (defn skip-chars + "Skip n characters" + [n] + (fn [s] [(.skip (unlock s) n) s])) + + (defn write + "Write text (a string)" + [#^String text] + (fn [s] [(.write (unlock s) text) s])) + + (defn flush + "Flush" + [] + (fn [s] [(.flush (unlock s)) s])) + + (defn print + "Print obj" + [obj] + (fn [s] [(.print (unlock s) obj) s])) + + (defn println + "Print obj followed by a newline" + ([] + (fn [s] [(.println (unlock s)) s])) + ([obj] + (fn [s] [(.println (unlock s) obj) s]))) + + ; + ; Inject I/O streams into monadic I/O statements + ; + (defn with-reader + "Create a reader from reader-spec, run the monadic I/O statement + on it, and close the reader. reader-spec can be any object accepted + by clojure.contrib.duck-streams/reader." + [reader-spec statement] + (with-open [r (reader reader-spec)] + (first (statement (lock r))))) + + (defn with-writer + "Create a writer from writer-spec, run the monadic I/O statement + on it, and close the writer. writer-spec can be any object accepted + by clojure.contrib.duck-streams/writer." + [writer-spec statement] + (with-open [w (writer writer-spec)] + (first (statement (lock w))))) + + (defn with-io-streams + "Open one or more streams as specified by io-spec, run a monadic + I/O statement on them, and close the streams. io-spec is + a binding-like vector in which each stream is specified by + three element: a keyword by which the stream can be referred to, + the stream mode (:read or :write), and a stream specification as + accepted by clojure.contrib.duck-streams/reader (mode :read) or + clojure.contrib.duck-streams/writer (mode :write). The statement + is run on a state which is a map from keywords to corresponding + streams. Single-stream monadic I/O statements must be wrapped + with clojure.contrib.monads/with-state-field." + [io-specs statement] + (letfn [(run-io [io-specs state statement] + (if (zero? (count io-specs)) + (first (statement state)) + (let [[[key mode stream-spec] & r] io-specs + opener (cond (= mode :read) reader + (= mode :write) writer + :else (throw + (Exception. + "Mode must be :read or :write")))] + (with-open [stream (opener stream-spec)] + (run-io r (assoc state key (lock stream)) statement)))))] + (run-io (partition 3 io-specs) {} statement)))) + +; +; Compound I/O statements +; +(with-monad state-m + + (defn- add-line + "Read one line and add it to the end of the vector lines. Return + [lines eof], where eof is an end-of-file flag. The input eof argument + is not used." + [[lines eof]] + (domonad + [line (read-line)] + (if (nil? line) + [lines true] + [(conj lines line) false]))) + + (defn read-lines + "Read all lines and return them in a vector" + [] + (domonad + [[lines eof] (state-m-until second add-line [[] false])] + lines))) + |