0
|
1 package fj.data;
|
|
2
|
|
3 import static fj.Bottom.errorF;
|
|
4 import static fj.Function.constant;
|
|
5 import static fj.Function.partialApply2;
|
|
6
|
|
7 import java.io.BufferedReader;
|
|
8 import java.io.File;
|
|
9 import java.io.FileInputStream;
|
|
10 import java.io.IOException;
|
|
11 import java.io.InputStreamReader;
|
|
12 import java.io.Reader;
|
|
13 import java.nio.charset.Charset;
|
|
14 import java.util.Arrays;
|
|
15
|
|
16 import fj.*;
|
|
17 import fj.data.Iteratee.Input;
|
|
18 import fj.data.Iteratee.IterV;
|
|
19 import fj.function.Try0;
|
|
20
|
|
21 /**
|
|
22 * IO monad for processing files, with main methods {@link #enumFileLines },
|
|
23 * {@link #enumFileChars} and {@link #enumFileCharChunks}
|
|
24 * (the latter one is the fastest as char chunks read from the file are directly passed to the iteratee
|
|
25 * without indirection in between).
|
|
26 *
|
|
27 * @author Martin Grotzke
|
|
28 *
|
|
29 */
|
|
30 public class IOFunctions {
|
|
31
|
|
32 private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
|
|
33
|
|
34 public static <A> Try0<A, IOException> toTry(IO<A> io) {
|
|
35 return () -> io.run();
|
|
36 }
|
|
37
|
|
38 public static <A> P1<Validation<IOException, A>> p(IO<A> io) {
|
|
39 return Try.f(toTry(io));
|
|
40 }
|
|
41
|
|
42 public static <A> IO<A> io(P1<A> p) {
|
|
43 return () -> p._1();
|
|
44 }
|
|
45
|
|
46 public static <A> IO<A> io(Try0<A, ? extends IOException> t) {
|
|
47 return () -> t.f();
|
|
48 }
|
|
49
|
|
50 public static final F<Reader, IO<Unit>> closeReader =
|
|
51 new F<Reader, IO<Unit>>() {
|
|
52 @Override
|
|
53 public IO<Unit> f(final Reader r) {
|
|
54 return closeReader(r);
|
|
55 }
|
|
56 };
|
|
57
|
|
58 public static IO<Unit> closeReader(final Reader r) {
|
|
59 return new IO<Unit>() {
|
|
60 @Override
|
|
61 public Unit run() throws IOException {
|
|
62 r.close();
|
|
63 return Unit.unit();
|
|
64 }
|
|
65 };
|
|
66 }
|
|
67
|
|
68 /**
|
|
69 * An IO monad that reads lines from the given file (using a {@link BufferedReader}) and passes
|
|
70 * lines to the provided iteratee. May not be suitable for files with very long
|
|
71 * lines, consider to use {@link #enumFileCharChunks} or {@link #enumFileChars}
|
|
72 * as an alternative.
|
|
73 *
|
|
74 * @param f the file to read, must not be <code>null</code>
|
|
75 * @param encoding the encoding to use, {@link Option#none()} means platform default
|
|
76 * @param i the iteratee that is fed with lines read from the file
|
|
77 */
|
|
78 public static <A> IO<IterV<String, A>> enumFileLines(final File f, final Option<Charset> encoding, final IterV<String, A> i) {
|
|
79 return bracket(bufferedReader(f, encoding)
|
|
80 , Function.<BufferedReader, IO<Unit>>vary(closeReader)
|
|
81 , partialApply2(IOFunctions.<A>lineReader(), i));
|
|
82 }
|
|
83
|
|
84 /**
|
|
85 * An IO monad that reads char chunks from the given file and passes them to the given iteratee.
|
|
86 *
|
|
87 * @param f the file to read, must not be <code>null</code>
|
|
88 * @param encoding the encoding to use, {@link Option#none()} means platform default
|
|
89 * @param i the iteratee that is fed with char chunks read from the file
|
|
90 */
|
|
91 public static <A> IO<IterV<char[], A>> enumFileCharChunks(final File f, final Option<Charset> encoding, final IterV<char[], A> i) {
|
|
92 return bracket(fileReader(f, encoding)
|
|
93 , Function.<Reader, IO<Unit>>vary(closeReader)
|
|
94 , partialApply2(IOFunctions.<A>charChunkReader(), i));
|
|
95 }
|
|
96
|
|
97 /**
|
|
98 * An IO monad that reads char chunks from the given file and passes single chars to the given iteratee.
|
|
99 *
|
|
100 * @param f the file to read, must not be <code>null</code>
|
|
101 * @param encoding the encoding to use, {@link Option#none()} means platform default
|
|
102 * @param i the iteratee that is fed with chars read from the file
|
|
103 */
|
|
104 public static <A> IO<IterV<Character, A>> enumFileChars(final File f, final Option<Charset> encoding, final IterV<Character, A> i) {
|
|
105 return bracket(fileReader(f, encoding)
|
|
106 , Function.<Reader, IO<Unit>>vary(closeReader)
|
|
107 , partialApply2(IOFunctions.<A>charChunkReader2(), i));
|
|
108 }
|
|
109
|
|
110 public static IO<BufferedReader> bufferedReader(final File f, final Option<Charset> encoding) {
|
|
111 return IOFunctions.map(fileReader(f, encoding), new F<Reader, BufferedReader>() {
|
|
112 @Override
|
|
113 public BufferedReader f(final Reader a) {
|
|
114 return new BufferedReader(a);
|
|
115 }});
|
|
116 }
|
|
117
|
|
118 public static IO<Reader> fileReader(final File f, final Option<Charset> encoding) {
|
|
119 return new IO<Reader>() {
|
|
120 @Override
|
|
121 public Reader run() throws IOException {
|
|
122 final FileInputStream fis = new FileInputStream(f);
|
|
123 return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some());
|
|
124 }
|
|
125 };
|
|
126 }
|
|
127
|
|
128 public static final <A, B, C> IO<C> bracket(final IO<A> init, final F<A, IO<B>> fin, final F<A, IO<C>> body) {
|
|
129 return new IO<C>() {
|
|
130 @Override
|
|
131 public C run() throws IOException {
|
|
132 final A a = init.run();
|
|
133 try {
|
|
134 return body.f(a).run();
|
|
135 } catch (final IOException e) {
|
|
136 throw e;
|
|
137 } finally {
|
|
138 fin.f(a);
|
|
139 }
|
|
140 }
|
|
141 };
|
|
142 }
|
|
143
|
|
144 public static final <A> IO<A> unit(final A a) {
|
|
145 return new IO<A>() {
|
|
146 @Override
|
|
147 public A run() throws IOException {
|
|
148 return a;
|
|
149 }
|
|
150 };
|
|
151 }
|
|
152
|
|
153 public static final <A> IO<A> lazy(final P1<A> p) {
|
|
154 return () -> p._1();
|
|
155 }
|
|
156
|
|
157 public static final <A> IO<A> lazy(final F<Unit, A> f) {
|
|
158 return () -> f.f(Unit.unit());
|
|
159 }
|
|
160
|
|
161 public static final <A> SafeIO<A> lazySafe(final F<Unit, A> f) {
|
|
162 return () -> f.f(Unit.unit());
|
|
163 }
|
|
164
|
|
165 public static final <A> SafeIO<A> lazySafe(final P1<A> f) {
|
|
166 return () -> f._1();
|
|
167 }
|
|
168
|
|
169 /**
|
|
170 * A function that feeds an iteratee with lines read from a {@link BufferedReader}.
|
|
171 */
|
|
172 public static <A> F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>> lineReader() {
|
|
173 final F<IterV<String, A>, Boolean> isDone =
|
|
174 new F<Iteratee.IterV<String, A>, Boolean>() {
|
|
175 final F<P2<A, Input<String>>, P1<Boolean>> done = constant(P.p(true));
|
|
176 final F<F<Input<String>, IterV<String, A>>, P1<Boolean>> cont = constant(P.p(false));
|
|
177
|
|
178 @Override
|
|
179 public Boolean f(final IterV<String, A> i) {
|
|
180 return i.fold(done, cont)._1();
|
|
181 }
|
|
182 };
|
|
183
|
|
184 return new F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>>() {
|
|
185 @Override
|
|
186 public F<IterV<String, A>, IO<IterV<String, A>>> f(final BufferedReader r) {
|
|
187 return new F<IterV<String, A>, IO<IterV<String, A>>>() {
|
|
188 final F<P2<A, Input<String>>, P1<IterV<String, A>>> done = errorF("iteratee is done"); //$NON-NLS-1$
|
|
189
|
|
190 @Override
|
|
191 public IO<IterV<String, A>> f(final IterV<String, A> it) {
|
|
192 // use loop instead of recursion because of missing TCO
|
|
193 return new IO<Iteratee.IterV<String, A>>() {
|
|
194 @Override
|
|
195 public IterV<String, A> run() throws IOException {
|
|
196 IterV<String, A> i = it;
|
|
197 while (!isDone.f(i)) {
|
|
198 final String s = r.readLine();
|
|
199 if (s == null) { return i; }
|
|
200 final Input<String> input = Input.<String>el(s);
|
|
201 final F<F<Input<String>, IterV<String, A>>, P1<IterV<String, A>>> cont = F1Functions.lazy(Function.<Input<String>, IterV<String, A>>apply(input));
|
|
202 i = i.fold(done, cont)._1();
|
|
203 }
|
|
204 return i;
|
|
205 }
|
|
206 };
|
|
207 }
|
|
208 };
|
|
209 }
|
|
210 };
|
|
211 }
|
|
212
|
|
213 /**
|
|
214 * A function that feeds an iteratee with character chunks read from a {@link Reader}
|
|
215 * (char[] of size {@link #DEFAULT_BUFFER_SIZE}).
|
|
216 */
|
|
217 public static <A> F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>> charChunkReader() {
|
|
218 final F<IterV<char[], A>, Boolean> isDone =
|
|
219 new F<Iteratee.IterV<char[], A>, Boolean>() {
|
|
220 final F<P2<A, Input<char[]>>, P1<Boolean>> done = constant(P.p(true));
|
|
221 final F<F<Input<char[]>, IterV<char[], A>>, P1<Boolean>> cont = constant(P.p(false));
|
|
222
|
|
223 @Override
|
|
224 public Boolean f(final IterV<char[], A> i) {
|
|
225 return i.fold(done, cont)._1();
|
|
226 }
|
|
227 };
|
|
228
|
|
229 return new F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>>() {
|
|
230 @Override
|
|
231 public F<IterV<char[], A>, IO<IterV<char[], A>>> f(final Reader r) {
|
|
232 return new F<IterV<char[], A>, IO<IterV<char[], A>>>() {
|
|
233 final F<P2<A, Input<char[]>>, P1<IterV<char[], A>>> done = errorF("iteratee is done"); //$NON-NLS-1$
|
|
234
|
|
235 @Override
|
|
236 public IO<IterV<char[], A>> f(final IterV<char[], A> it) {
|
|
237 // use loop instead of recursion because of missing TCO
|
|
238 return new IO<Iteratee.IterV<char[], A>>() {
|
|
239 @Override
|
|
240 public IterV<char[], A> run() throws IOException {
|
|
241
|
|
242 IterV<char[], A> i = it;
|
|
243 while (!isDone.f(i)) {
|
|
244 char[] buffer = new char[DEFAULT_BUFFER_SIZE];
|
|
245 final int numRead = r.read(buffer);
|
|
246 if (numRead == -1) { return i; }
|
|
247 if(numRead < buffer.length) {
|
|
248 buffer = Arrays.copyOfRange(buffer, 0, numRead);
|
|
249 }
|
|
250 final Input<char[]> input = Input.<char[]>el(buffer);
|
|
251 final F<F<Input<char[]>, IterV<char[], A>>, P1<IterV<char[], A>>> cont =
|
|
252 F1Functions.lazy(Function.<Input<char[]>, IterV<char[], A>>apply(input));
|
|
253 i = i.fold(done, cont)._1();
|
|
254 }
|
|
255 return i;
|
|
256 }
|
|
257 };
|
|
258 }
|
|
259 };
|
|
260 }
|
|
261 };
|
|
262 }
|
|
263
|
|
264 /**
|
|
265 * A function that feeds an iteratee with characters read from a {@link Reader}
|
|
266 * (chars are read in chunks of size {@link #DEFAULT_BUFFER_SIZE}).
|
|
267 */
|
|
268 public static <A> F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>> charChunkReader2() {
|
|
269 final F<IterV<Character, A>, Boolean> isDone =
|
|
270 new F<Iteratee.IterV<Character, A>, Boolean>() {
|
|
271 final F<P2<A, Input<Character>>, P1<Boolean>> done = constant(P.p(true));
|
|
272 final F<F<Input<Character>, IterV<Character, A>>, P1<Boolean>> cont = constant(P.p(false));
|
|
273
|
|
274 @Override
|
|
275 public Boolean f(final IterV<Character, A> i) {
|
|
276 return i.fold(done, cont)._1();
|
|
277 }
|
|
278 };
|
|
279
|
|
280 return new F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>>() {
|
|
281 @Override
|
|
282 public F<IterV<Character, A>, IO<IterV<Character, A>>> f(final Reader r) {
|
|
283 return new F<IterV<Character, A>, IO<IterV<Character, A>>>() {
|
|
284 final F<P2<A, Input<Character>>, IterV<Character, A>> done = errorF("iteratee is done"); //$NON-NLS-1$
|
|
285
|
|
286 @Override
|
|
287 public IO<IterV<Character, A>> f(final IterV<Character, A> it) {
|
|
288 // use loop instead of recursion because of missing TCO
|
|
289 return new IO<Iteratee.IterV<Character, A>>() {
|
|
290 @Override
|
|
291 public IterV<Character, A> run() throws IOException {
|
|
292
|
|
293 IterV<Character, A> i = it;
|
|
294 while (!isDone.f(i)) {
|
|
295 char[] buffer = new char[DEFAULT_BUFFER_SIZE];
|
|
296 final int numRead = r.read(buffer);
|
|
297 if (numRead == -1) { return i; }
|
|
298 if(numRead < buffer.length) {
|
|
299 buffer = Arrays.copyOfRange(buffer, 0, numRead);
|
|
300 }
|
|
301 for(int c = 0; c < buffer.length; c++) {
|
|
302 final Input<Character> input = Input.el(buffer[c]);
|
|
303 final F<F<Input<Character>, IterV<Character, A>>, IterV<Character, A>> cont =
|
|
304 Function.<Input<Character>, IterV<Character, A>>apply(input);
|
|
305 i = i.fold(done, cont);
|
|
306 }
|
|
307 }
|
|
308 return i;
|
|
309 }
|
|
310 };
|
|
311 }
|
|
312 };
|
|
313 }
|
|
314 };
|
|
315 }
|
|
316
|
|
317 public static final <A, B> IO<B> map(final IO<A> io, final F<A, B> f) {
|
|
318 return new IO<B>() {
|
|
319 @Override
|
|
320 public B run() throws IOException {
|
|
321 return f.f(io.run());
|
|
322 }
|
|
323 };
|
|
324 }
|
|
325
|
|
326 public static final <A, B> IO<B> bind(final IO<A> io, final F<A, IO<B>> f) {
|
|
327 return new IO<B>() {
|
|
328 @Override
|
|
329 public B run() throws IOException {
|
|
330 return f.f(io.run()).run();
|
|
331 }
|
|
332 };
|
|
333 }
|
|
334
|
|
335 /**
|
|
336 * Evaluate each action in the sequence from left to right, and collect the results.
|
|
337 */
|
|
338 public static <A> IO<List<A>> sequence(List<IO<A>> list) {
|
|
339 F2<IO<A>, IO<List<A>>, IO<List<A>>> f2 = (io, ioList) ->
|
|
340 IOFunctions.bind(ioList, (xs) -> map(io, x -> List.cons(x, xs)));
|
|
341 return list.foldRight(f2, IOFunctions.unit(List.<A>nil()));
|
|
342 }
|
|
343
|
|
344
|
|
345 public static <A> IO<Stream<A>> sequence(Stream<IO<A>> stream) {
|
|
346 F2<IO<Stream<A>>, IO<A>, IO<Stream<A>>> f2 = (ioList, io) ->
|
|
347 IOFunctions.bind(ioList, (xs) -> map(io, x -> Stream.cons(x, P.lazy(u -> xs))));
|
|
348 return stream.foldLeft(f2, IOFunctions.unit(Stream.<A>nil()));
|
|
349 }
|
|
350
|
|
351
|
|
352 /**
|
|
353 * Map each element of a structure to an action, evaluate these actions from left to right
|
|
354 * and collect the results.
|
|
355 */
|
|
356 public static <A, B> IO<List<B>> traverse(List<A> list, F<A, IO<B>> f) {
|
|
357 F2<A, IO<List<B>>, IO<List<B>>> f2 = (a, acc) ->
|
|
358 bind(acc, (bs) -> map(f.f(a), b -> bs.append(List.list(b))));
|
|
359 return list.foldRight(f2, IOFunctions.unit(List.<B>nil()));
|
|
360 }
|
|
361
|
|
362 public static <A> IO<A> join(IO<IO<A>> io1) {
|
|
363 return bind(io1, io2 -> io2);
|
|
364 }
|
|
365
|
|
366 public static <A> SafeIO<Validation<IOException, A>> toSafeIO(IO<A> io) {
|
|
367 return () -> Try.f(() -> io.run())._1();
|
|
368 }
|
|
369
|
|
370 public static <A, B> IO<B> append(final IO<A> io1, final IO<B> io2) {
|
|
371 return () -> {
|
|
372 io1.run();
|
|
373 return io2.run();
|
|
374 };
|
|
375 }
|
|
376
|
|
377 public static <A, B> IO<A> left(final IO<A> io1, final IO<B> io2) {
|
|
378 return () -> {
|
|
379 A a = io1.run();
|
|
380 io2.run();
|
|
381 return a;
|
|
382 };
|
|
383 }
|
|
384
|
|
385 public static <A, B> IO<B> flatMap(final IO<A> io, final F<A, IO<B>> f) {
|
|
386 return bind(io, f);
|
|
387 }
|
|
388
|
|
389 static <A> IO<Stream<A>> sequenceWhile(final Stream<IO<A>> stream, final F<A, Boolean> f) {
|
|
390 return new IO<Stream<A>>() {
|
|
391 @Override
|
|
392 public Stream<A> run() throws IOException {
|
|
393 boolean loop = true;
|
|
394 Stream<IO<A>> input = stream;
|
|
395 Stream<A> result = Stream.<A>nil();
|
|
396 while (loop) {
|
|
397 if (input.isEmpty()) {
|
|
398 loop = false;
|
|
399 } else {
|
|
400 A a = input.head().run();
|
|
401 if (!f.f(a)) {
|
|
402 loop = false;
|
|
403 } else {
|
|
404 input = input.tail()._1();
|
|
405 result = result.cons(a);
|
|
406 }
|
|
407 }
|
|
408 }
|
|
409 return result.reverse();
|
|
410 }
|
|
411 };
|
|
412 }
|
|
413
|
|
414 public static <A, B> IO<B> apply(IO<A> io, IO<F<A, B>> iof) {
|
|
415 return bind(iof, f -> map(io, a -> f.f(a)));
|
|
416 }
|
|
417
|
|
418 public static <A, B, C> IO<C> liftM2(IO<A> ioa, IO<B> iob, F2<A, B, C> f) {
|
|
419 return bind(ioa, a -> map(iob, b -> f.f(a, b)));
|
|
420 }
|
|
421
|
|
422 public static <A> IO<List<A>> replicateM(IO<A> ioa, int n) {
|
|
423 return sequence(List.replicate(n, ioa));
|
|
424 }
|
|
425
|
|
426 public static <A> IO<State<BufferedReader, Validation<IOException, String>>> readerState() {
|
|
427 return () -> State.unit((BufferedReader r) -> P.p(r, Try.f((BufferedReader r2) -> r2.readLine()).f(r)));
|
|
428 }
|
|
429
|
|
430 public static final BufferedReader stdinBufferedReader = new BufferedReader(new InputStreamReader(System.in));
|
|
431
|
|
432 public static IO<String> stdinReadLine() {
|
|
433 return () -> stdinBufferedReader.readLine();
|
|
434 }
|
|
435
|
|
436 public static IO<Unit> stdoutPrintln(final String s) {
|
|
437 return () -> {
|
|
438 System.out.println(s);
|
|
439 return Unit.unit();
|
|
440 };
|
|
441 }
|
|
442
|
|
443 }
|