view src/clojure/contrib/monadic_io_streams.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line source
1 ;; Monadic I/O
3 ;; by Konrad Hinsen
4 ;; last updated June 24, 2009
6 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use
7 ;; and distribution terms for this software are covered by the Eclipse
8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
9 ;; which can be found in the file epl-v10.html at the root of this
10 ;; distribution. By using this software in any fashion, you are
11 ;; agreeing to be bound by the terms of this license. You must not
12 ;; remove this notice, or any other, from this software.
14 (ns
15 ^{:author "Konrad Hinsen"
16 :doc "Monadic I/O with Java input/output streams
17 Defines monadic I/O statements to be used in a state monad
18 with an input or output stream as the state. The macro
19 monadic-io creates a stream, runs a monadic I/O statement
20 on it, and closes the stream. This structure permits the
21 definition of purely functional compound I/O statements
22 which are applied to streams that can never escape from the
23 monadic statement sequence."}
24 clojure.contrib.monadic-io-streams
25 (:refer-clojure :exclude (read-line print println flush))
26 (:use [clojure.contrib.monads
27 :only (with-monad domonad state-m state-m-until)])
28 (:use [clojure.contrib.generic.functor :only (fmap)])
29 (:use [clojure.java.io :only (reader writer)]))
31 ;
32 ; Wrap the state into a closure to make sure that "evil" code
33 ; can't obtain the stream using fetch-state and manipulate it.
34 ;
35 (let [key (Object.)
36 lock (fn [state] (fn [x] (if (identical? x key) state nil)))
37 unlock (fn [state] (state key))]
39 ;
40 ; Basic stream I/O statements as provided by Java
41 ;
42 (defn read-char
43 "Read a single character"
44 []
45 (fn [s] [(.read (unlock s)) s]))
47 (defn read-line
48 "Read a single line"
49 []
50 (fn [s] [(.readLine (unlock s)) s]))
52 (defn skip-chars
53 "Skip n characters"
54 [n]
55 (fn [s] [(.skip (unlock s) n) s]))
57 (defn write
58 "Write text (a string)"
59 [^String text]
60 (fn [s] [(.write (unlock s) text) s]))
62 (defn flush
63 "Flush"
64 []
65 (fn [s] [(.flush (unlock s)) s]))
67 (defn print
68 "Print obj"
69 [obj]
70 (fn [s] [(.print (unlock s) obj) s]))
72 (defn println
73 "Print obj followed by a newline"
74 ([]
75 (fn [s] [(.println (unlock s)) s]))
76 ([obj]
77 (fn [s] [(.println (unlock s) obj) s])))
79 ;
80 ; Inject I/O streams into monadic I/O statements
81 ;
82 (defn with-reader
83 "Create a reader from reader-spec, run the monadic I/O statement
84 on it, and close the reader. reader-spec can be any object accepted
85 by clojure.contrib.io/reader."
86 [reader-spec statement]
87 (with-open [r (reader reader-spec)]
88 (first (statement (lock r)))))
90 (defn with-writer
91 "Create a writer from writer-spec, run the monadic I/O statement
92 on it, and close the writer. writer-spec can be any object accepted
93 by clojure.contrib.io/writer."
94 [writer-spec statement]
95 (with-open [w (writer writer-spec)]
96 (first (statement (lock w)))))
98 (defn with-io-streams
99 "Open one or more streams as specified by io-spec, run a monadic
100 I/O statement on them, and close the streams. io-spec is
101 a binding-like vector in which each stream is specified by
102 three element: a keyword by which the stream can be referred to,
103 the stream mode (:read or :write), and a stream specification as
104 accepted by clojure.contrib.io/reader (mode :read) or
105 clojure.contrib.io/writer (mode :write). The statement
106 is run on a state which is a map from keywords to corresponding
107 streams. Single-stream monadic I/O statements must be wrapped
108 with clojure.contrib.monads/with-state-field."
109 [io-specs statement]
110 (letfn [(run-io [io-specs state statement]
111 (if (zero? (count io-specs))
112 (first (statement state))
113 (let [[[key mode stream-spec] & r] io-specs
114 opener (cond (= mode :read) reader
115 (= mode :write) writer
116 :else (throw
117 (Exception.
118 "Mode must be :read or :write")))]
119 (with-open [stream (opener stream-spec)]
120 (run-io r (assoc state key (lock stream)) statement)))))]
121 (run-io (partition 3 io-specs) {} statement))))
123 ;
124 ; Compound I/O statements
125 ;
126 (with-monad state-m
128 (defn- add-line
129 "Read one line and add it to the end of the vector lines. Return
130 [lines eof], where eof is an end-of-file flag. The input eof argument
131 is not used."
132 [[lines eof]]
133 (domonad
134 [line (read-line)]
135 (if (nil? line)
136 [lines true]
137 [(conj lines line) false])))
139 (defn read-lines
140 "Read all lines and return them in a vector"
141 []
142 (domonad
143 [[lines eof] (state-m-until second add-line [[] false])]
144 lines)))