Mercurial > hg > Members > tatsuki > functionaljava-master > core
diff src/main/java/fj/data/IOFunctions.java @ 0:fe80c1edf1be
add getLoop
author | tatsuki |
---|---|
date | Fri, 20 Mar 2015 21:04:03 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/fj/data/IOFunctions.java Fri Mar 20 21:04:03 2015 +0900 @@ -0,0 +1,443 @@ +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.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; + +/** + * 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 class IOFunctions { + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + 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> io(P1<A> p) { + return () -> p._1(); + } + + public static <A> IO<A> io(Try0<A, ? extends IOException> t) { + return () -> t.f(); + } + + public static final F<Reader, IO<Unit>> closeReader = + new F<Reader, IO<Unit>>() { + @Override + public IO<Unit> f(final Reader r) { + return closeReader(r); + } + }; + + public static IO<Unit> closeReader(final Reader r) { + return new IO<Unit>() { + @Override + public Unit run() throws IOException { + 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.<BufferedReader, IO<Unit>>vary(closeReader) + , partialApply2(IOFunctions.<A>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.<Reader, IO<Unit>>vary(closeReader) + , partialApply2(IOFunctions.<A>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.<Reader, IO<Unit>>vary(closeReader) + , partialApply2(IOFunctions.<A>charChunkReader2(), i)); + } + + public static IO<BufferedReader> bufferedReader(final File f, final Option<Charset> encoding) { + return IOFunctions.map(fileReader(f, encoding), new F<Reader, BufferedReader>() { + @Override + public BufferedReader f(final Reader a) { + return new BufferedReader(a); + }}); + } + + public static IO<Reader> fileReader(final File f, final Option<Charset> encoding) { + return new IO<Reader>() { + @Override + public Reader run() throws IOException { + final FileInputStream fis = new FileInputStream(f); + return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some()); + } + }; + } + + 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) { + return new IO<C>() { + @Override + public C run() throws IOException { + final A a = init.run(); + try { + return body.f(a).run(); + } catch (final IOException e) { + throw e; + } finally { + fin.f(a); + } + } + }; + } + + public static final <A> IO<A> unit(final A a) { + return new IO<A>() { + @Override + public A run() throws IOException { + return a; + } + }; + } + + public static final <A> IO<A> lazy(final P1<A> p) { + return () -> p._1(); + } + + public static final <A> IO<A> lazy(final F<Unit, A> f) { + return () -> f.f(Unit.unit()); + } + + public static final <A> SafeIO<A> lazySafe(final F<Unit, A> f) { + return () -> f.f(Unit.unit()); + } + + public static final <A> SafeIO<A> lazySafe(final P1<A> f) { + return () -> f._1(); + } + + /** + * 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 new F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>>() { + @Override + public F<IterV<String, A>, IO<IterV<String, A>>> f(final BufferedReader r) { + return 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 new IO<Iteratee.IterV<String, A>>() { + @Override + public IterV<String, A> run() throws IOException { + IterV<String, A> i = it; + while (!isDone.f(i)) { + final String s = r.readLine(); + if (s == null) { return i; } + final Input<String> input = Input.<String>el(s); + final F<F<Input<String>, IterV<String, A>>, P1<IterV<String, A>>> cont = F1Functions.lazy(Function.<Input<String>, IterV<String, A>>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 new F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>>() { + @Override + public F<IterV<char[], A>, IO<IterV<char[], A>>> f(final Reader r) { + return 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 new IO<Iteratee.IterV<char[], A>>() { + @Override + public IterV<char[], A> run() throws IOException { + + 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.<char[]>el(buffer); + final F<F<Input<char[]>, IterV<char[], A>>, P1<IterV<char[], A>>> cont = + F1Functions.lazy(Function.<Input<char[]>, IterV<char[], A>>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 new F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>>() { + @Override + public F<IterV<Character, A>, IO<IterV<Character, A>>> f(final Reader r) { + return 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 new IO<Iteratee.IterV<Character, A>>() { + @Override + public IterV<Character, A> run() throws IOException { + + 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(int c = 0; c < buffer.length; c++) { + final Input<Character> input = Input.el(buffer[c]); + final F<F<Input<Character>, IterV<Character, A>>, IterV<Character, A>> cont = + Function.<Input<Character>, IterV<Character, A>>apply(input); + i = i.fold(done, cont); + } + } + return i; + } + }; + } + }; + } + }; + } + + public static final <A, B> IO<B> map(final IO<A> io, final F<A, B> f) { + return new IO<B>() { + @Override + public B run() throws IOException { + return f.f(io.run()); + } + }; + } + + public static final <A, B> IO<B> bind(final IO<A> io, final F<A, IO<B>> f) { + return new IO<B>() { + @Override + public B run() throws IOException { + return f.f(io.run()).run(); + } + }; + } + + /** + * 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) -> + IOFunctions.bind(ioList, (xs) -> map(io, x -> List.cons(x, xs))); + return list.foldRight(f2, IOFunctions.unit(List.<A>nil())); + } + + + public static <A> IO<Stream<A>> sequence(Stream<IO<A>> stream) { + F2<IO<Stream<A>>, IO<A>, IO<Stream<A>>> f2 = (ioList, io) -> + IOFunctions.bind(ioList, (xs) -> map(io, x -> Stream.cons(x, P.lazy(u -> xs)))); + return stream.foldLeft(f2, IOFunctions.unit(Stream.<A>nil())); + } + + + /** + * Map each element of a structure to an action, evaluate these actions from left to right + * and collect the results. + */ + public static <A, B> IO<List<B>> traverse(List<A> list, F<A, IO<B>> f) { + F2<A, IO<List<B>>, IO<List<B>>> f2 = (a, acc) -> + bind(acc, (bs) -> map(f.f(a), b -> bs.append(List.list(b)))); + return list.foldRight(f2, IOFunctions.unit(List.<B>nil())); + } + + public static <A> IO<A> join(IO<IO<A>> io1) { + return bind(io1, io2 -> io2); + } + + public static <A> SafeIO<Validation<IOException, A>> toSafeIO(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); + } + + static <A> IO<Stream<A>> sequenceWhile(final Stream<IO<A>> stream, final F<A, Boolean> f) { + return new IO<Stream<A>>() { + @Override + public Stream<A> run() throws IOException { + boolean loop = true; + Stream<IO<A>> input = stream; + Stream<A> result = Stream.<A>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, B> IO<B> apply(IO<A> io, IO<F<A, B>> iof) { + return bind(iof, f -> map(io, a -> f.f(a))); + } + + 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((BufferedReader r2) -> r2.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(); + }; + } + +}