aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKonrad Hinsen <konrad.hinsen@laposte.net>2009-06-24 16:49:50 +0200
committerKonrad Hinsen <konrad.hinsen@laposte.net>2009-06-24 16:49:50 +0200
commitf9b111c67752220c9d45a7d6ef22c6eecf400c87 (patch)
treeec13872717ca70f8827a213c6f0a492a5a90f837 /src
parent6823c51380b69ec12f641fbef09d395237931e40 (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.clj145
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)))
+