Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

146 lines (127 sloc) 4.8 kb
;; 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 (
;; 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.
#^{: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."}
(: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 [ :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"
(fn [s] [(.skip (unlock s) n) s]))
(defn write
"Write text (a string)"
[#^String text]
(fn [s] [(.write (unlock s) text) s]))
(defn flush
(fn [s] [(.flush (unlock s)) s]))
(defn print
"Print obj"
(fn [s] [(.print (unlock s) obj) s]))
(defn println
"Print obj followed by a newline"
(fn [s] [(.println (unlock s)) s]))
(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
[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
[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 (mode :read) or (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
"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]]
[line (read-line)]
(if (nil? line)
[lines true]
[(conj lines line) false])))
(defn read-lines
"Read all lines and return them in a vector"
[[lines eof] (state-m-until second add-line [[] false])]
Jump to Line
Something went wrong with that request. Please try again.