package fj.data; import static fj.Bottom.errorF; import static fj.Function.constant; import static fj.Function.partialApply2; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.util.Arrays; import fj.*; import fj.data.Iteratee.Input; import fj.data.Iteratee.IterV; import fj.function.Try0; import fj.function.Try1; /** * IO monad for processing files, with main methods {@link #enumFileLines }, * {@link #enumFileChars} and {@link #enumFileCharChunks} * (the latter one is the fastest as char chunks read from the file are directly passed to the iteratee * without indirection in between). * * @author Martin Grotzke */ public final class IOFunctions { private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; private IOFunctions() { } public static <A> Try0<A, IOException> toTry(IO<A> io) { return io::run; } public static <A> P1<Validation<IOException, A>> p(IO<A> io) { return Try.f(toTry(io)); } public static <A> IO<A> fromF(F0<A> p) { return p::f; } public static <A> IO<A> fromTry(Try0<A, ? extends IOException> t) { return t::f; } public static final F<Reader, IO<Unit>> closeReader = IOFunctions::closeReader; /** * Convert io to a SafeIO, throwing any IOException wrapped inside a RuntimeException * @param io */ public static <A> SafeIO<A> toSafe(IO<A> io) { return () -> { try { return io.run(); } catch (IOException e) { throw new RuntimeException(e); } }; } /** * Run io, rethrowing any IOException wrapped in a RuntimeException * @param io */ public static <A> A runSafe(IO<A> io) { return toSafe(io).run(); } public static IO<Unit> closeReader(final Reader r) { return () -> { r.close(); return Unit.unit(); }; } /** * An IO monad that reads lines from the given file (using a {@link BufferedReader}) and passes * lines to the provided iteratee. May not be suitable for files with very long * lines, consider to use {@link #enumFileCharChunks} or {@link #enumFileChars} * as an alternative. * * @param f the file to read, must not be <code>null</code> * @param encoding the encoding to use, {@link Option#none()} means platform default * @param i the iteratee that is fed with lines read from the file */ public static <A> IO<IterV<String, A>> enumFileLines(final File f, final Option<Charset> encoding, final IterV<String, A> i) { return bracket(bufferedReader(f, encoding) , Function.vary(closeReader) , partialApply2(IOFunctions.lineReader(), i)); } /** * An IO monad that reads char chunks from the given file and passes them to the given iteratee. * * @param f the file to read, must not be <code>null</code> * @param encoding the encoding to use, {@link Option#none()} means platform default * @param i the iteratee that is fed with char chunks read from the file */ public static <A> IO<IterV<char[], A>> enumFileCharChunks(final File f, final Option<Charset> encoding, final IterV<char[], A> i) { return bracket(fileReader(f, encoding) , Function.vary(closeReader) , partialApply2(IOFunctions.charChunkReader(), i)); } /** * An IO monad that reads char chunks from the given file and passes single chars to the given iteratee. * * @param f the file to read, must not be <code>null</code> * @param encoding the encoding to use, {@link Option#none()} means platform default * @param i the iteratee that is fed with chars read from the file */ public static <A> IO<IterV<Character, A>> enumFileChars(final File f, final Option<Charset> encoding, final IterV<Character, A> i) { return bracket(fileReader(f, encoding) , Function.vary(closeReader) , partialApply2(IOFunctions.charChunkReader2(), i)); } public static IO<BufferedReader> bufferedReader(final File f, final Option<Charset> encoding) { return map(fileReader(f, encoding), BufferedReader::new); } public static IO<Reader> fileReader(final File f, final Option<Charset> encoding) { return () -> { final FileInputStream fis = new FileInputStream(f); return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some()); }; } public static <A, B, C> IO<C> bracket(final IO<A> init, final F<A, IO<B>> fin, final F<A, IO<C>> body) { return () -> { final A a = init.run(); try(Closeable finAsCloseable = fin.f(a)::run) { return body.f(a).run(); } }; } public static <A> IO<A> unit(final A a) { return () -> a; } public static final IO<Unit> ioUnit = unit(Unit.unit()); public static <A> IO<A> lazy(final F0<A> p) { return fromF(p); } public static <A> IO<A> lazy(final F<Unit, A> f) { return () -> f.f(Unit.unit()); } public static <A> SafeIO<A> lazySafe(final F<Unit, A> f) { return () -> f.f(Unit.unit()); } public static <A> SafeIO<A> lazySafe(final F0<A> f) { return f::f; } /** * A function that feeds an iteratee with lines read from a {@link BufferedReader}. */ public static <A> F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>> lineReader() { final F<IterV<String, A>, Boolean> isDone = new F<Iteratee.IterV<String, A>, Boolean>() { final F<P2<A, Input<String>>, P1<Boolean>> done = constant(P.p(true)); final F<F<Input<String>, IterV<String, A>>, P1<Boolean>> cont = constant(P.p(false)); @Override public Boolean f(final IterV<String, A> i) { return i.fold(done, cont)._1(); } }; return r -> new F<IterV<String, A>, IO<IterV<String, A>>>() { final F<P2<A, Input<String>>, P1<IterV<String, A>>> done = errorF("iteratee is done"); //$NON-NLS-1$ @Override public IO<IterV<String, A>> f(final IterV<String, A> it) { // use loop instead of recursion because of missing TCO return () -> { IterV<String, A> i = it; while (!isDone.f(i)) { final String s = r.readLine(); if (s == null) { return i; } final Input<String> input = Input.el(s); final F<F<Input<String>, IterV<String, A>>, P1<IterV<String, A>>> cont = F1Functions.lazy(Function.apply(input)); i = i.fold(done, cont)._1(); } return i; }; } }; } /** * A function that feeds an iteratee with character chunks read from a {@link Reader} * (char[] of size {@link #DEFAULT_BUFFER_SIZE}). */ public static <A> F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>> charChunkReader() { final F<IterV<char[], A>, Boolean> isDone = new F<Iteratee.IterV<char[], A>, Boolean>() { final F<P2<A, Input<char[]>>, P1<Boolean>> done = constant(P.p(true)); final F<F<Input<char[]>, IterV<char[], A>>, P1<Boolean>> cont = constant(P.p(false)); @Override public Boolean f(final IterV<char[], A> i) { return i.fold(done, cont)._1(); } }; return r -> new F<IterV<char[], A>, IO<IterV<char[], A>>>() { final F<P2<A, Input<char[]>>, P1<IterV<char[], A>>> done = errorF("iteratee is done"); //$NON-NLS-1$ @Override public IO<IterV<char[], A>> f(final IterV<char[], A> it) { // use loop instead of recursion because of missing TCO return () -> { IterV<char[], A> i = it; while (!isDone.f(i)) { char[] buffer = new char[DEFAULT_BUFFER_SIZE]; final int numRead = r.read(buffer); if (numRead == -1) { return i; } if (numRead < buffer.length) { buffer = Arrays.copyOfRange(buffer, 0, numRead); } final Input<char[]> input = Input.el(buffer); final F<F<Input<char[]>, IterV<char[], A>>, P1<IterV<char[], A>>> cont = F1Functions.lazy(Function.apply(input)); i = i.fold(done, cont)._1(); } return i; }; } }; } /** * A function that feeds an iteratee with characters read from a {@link Reader} * (chars are read in chunks of size {@link #DEFAULT_BUFFER_SIZE}). */ public static <A> F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>> charChunkReader2() { final F<IterV<Character, A>, Boolean> isDone = new F<Iteratee.IterV<Character, A>, Boolean>() { final F<P2<A, Input<Character>>, P1<Boolean>> done = constant(P.p(true)); final F<F<Input<Character>, IterV<Character, A>>, P1<Boolean>> cont = constant(P.p(false)); @Override public Boolean f(final IterV<Character, A> i) { return i.fold(done, cont)._1(); } }; return r -> new F<IterV<Character, A>, IO<IterV<Character, A>>>() { final F<P2<A, Input<Character>>, IterV<Character, A>> done = errorF("iteratee is done"); //$NON-NLS-1$ @Override public IO<IterV<Character, A>> f(final IterV<Character, A> it) { // use loop instead of recursion because of missing TCO return () -> { IterV<Character, A> i = it; while (!isDone.f(i)) { char[] buffer = new char[DEFAULT_BUFFER_SIZE]; final int numRead = r.read(buffer); if (numRead == -1) { return i; } if (numRead < buffer.length) { buffer = Arrays.copyOfRange(buffer, 0, numRead); } for (char c : buffer) { final Input<Character> input = Input.el(c); final F<F<Input<Character>, IterV<Character, A>>, IterV<Character, A>> cont = Function.apply(input); i = i.fold(done, cont); } } return i; }; } }; } public static <A, B> IO<B> map(final IO<A> io, final F<A, B> f) { return () -> f.f(io.run()); } public static <A, B> IO<B> as(final IO<A> io, final B b) { return map(io, ignored -> b); } public static <A> IO<Unit> voided(final IO <A> io) { return as(io, Unit.unit()); } public static <A, B> IO<B> bind(final IO<A> io, final F<A, IO<B>> f) { return () -> f.f(io.run()).run(); } public static IO<Unit> when(final Boolean b, final IO<Unit> io) { return b ? io : ioUnit; } public static IO<Unit> unless(final Boolean b, final IO<Unit> io) { return when(!b, io); } /** * Evaluate each action in the sequence from left to right, and collect the results. */ public static <A> IO<List<A>> sequence(List<IO<A>> list) { F2<IO<A>, IO<List<A>>, IO<List<A>>> f2 = (io, ioList) -> bind(ioList, (xs) -> map(io, x -> List.cons(x, xs))); return list.foldRight(f2, unit(List.nil())); } public static <A> IO<Stream<A>> sequence(Stream<IO<A>> stream) { F2<IO<Stream<A>>, IO<A>, IO<Stream<A>>> f2 = (ioList, io) -> bind(ioList, (xs) -> map(io, x -> Stream.cons(x, () -> xs))); return stream.foldLeft(f2, unit(Stream.nil())); } public static <A> IO<A> join(IO<IO<A>> io1) { return bind(io1, io2 -> io2); } public static <A> SafeIO<Validation<IOException, A>> toSafeValidation(IO<A> io) { return () -> Try.f(io::run)._1(); } public static <A, B> IO<B> append(final IO<A> io1, final IO<B> io2) { return () -> { io1.run(); return io2.run(); }; } public static <A, B> IO<A> left(final IO<A> io1, final IO<B> io2) { return () -> { A a = io1.run(); io2.run(); return a; }; } public static <A, B> IO<B> flatMap(final IO<A> io, final F<A, IO<B>> f) { return bind(io, f); } /** * Read lines from stdin until condition is not met, transforming each line and printing * the result to stdout. * @param condition Read lines until a line does not satisfy condition * @param transform Function to change line value */ public static IO<Unit> interactWhile(F<String, Boolean> condition, F<String, String> transform) { Stream<IO<String>> s1 = Stream.repeat(stdinReadLine()); IO<Stream<String>> io = sequenceWhile(s1, condition); return () -> runSafe(io).foreach(s -> runSafe(stdoutPrintln(transform.f(s)))); } public static <A> IO<Stream<A>> sequenceWhileEager(final Stream<IO<A>> stream, final F<A, Boolean> f) { return () -> { boolean loop = true; Stream<IO<A>> input = stream; Stream<A> result = Stream.nil(); while (loop) { if (input.isEmpty()) { loop = false; } else { A a = input.head().run(); if (!f.f(a)) { loop = false; } else { input = input.tail()._1(); result = result.cons(a); } } } return result.reverse(); }; } public static <A> IO<Stream<A>> sequenceWhile(final Stream<IO<A>> stream, final F<A, Boolean> f) { return () -> { if (stream.isEmpty()) { return Stream.nil(); } else { IO<A> io = stream.head(); A a = io.run(); if (!f.f(a)) { return Stream.nil(); } else { IO<Stream<A>> io2 = sequenceWhile(stream.tail()._1(), f); SafeIO<Stream<A>> s3 = toSafe(io2::run); return Stream.cons(a, s3::run); } } }; } public static <A, B> IO<B> apply(IO<A> io, IO<F<A, B>> iof) { return bind(iof, f -> map(io, f)); } public static <A, B, C> IO<C> liftM2(IO<A> ioa, IO<B> iob, F2<A, B, C> f) { return bind(ioa, a -> map(iob, b -> f.f(a, b))); } public static <A> IO<List<A>> replicateM(IO<A> ioa, int n) { return sequence(List.replicate(n, ioa)); } public static <A> IO<State<BufferedReader, Validation<IOException, String>>> readerState() { return () -> State.unit((BufferedReader r) -> P.p(r, Try.f((Try1<BufferedReader, String, IOException>) BufferedReader::readLine).f(r))); } public static final BufferedReader stdinBufferedReader = new BufferedReader(new InputStreamReader(System.in)); public static IO<String> stdinReadLine() { return stdinBufferedReader::readLine; } public static IO<Unit> stdoutPrintln(final String s) { return () -> { System.out.println(s); return Unit.unit(); }; } public static IO<Unit> stdoutPrint(final String s) { return () -> { System.out.print(s); return Unit.unit(); }; } public static IO<LazyString> getContents() { Stream<IO<Integer>> s = Stream.repeat(() -> stdinBufferedReader.read()); return map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.map(i -> (char) i.intValue()))); } public static IO<Unit> interact(F<LazyString, LazyString> f) { return bind(getContents(), ls1 -> { LazyString ls2 = f.f(ls1); return stdoutPrintln(ls2.toString()); }); } }