fj.data.IOFunctions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of functionaljava Show documentation
Show all versions of functionaljava Show documentation
Functional Java is an open source library that supports closures for the Java programming language
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 Try0 toTry(IO io) {
return () -> io.run();
}
public static P1> p(IO io) {
return Try.f(toTry(io));
}
public static IO fromF(F0 p) {
return p::f;
}
public static IO fromTry(Try0 t) {
return () -> t.f();
}
public static final F> closeReader =
new F>() {
@Override
public IO f(final Reader r) {
return closeReader(r);
}
};
/**
* Convert io to a SafeIO, throwing any IOException wrapped inside a RuntimeException
* @param io
*/
public static SafeIO toSafe(IO 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 runSafe(IO io) {
return toSafe(io).run();
}
public static IO closeReader(final Reader r) {
return new IO() {
@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 null
* @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 IO> enumFileLines(final File f, final Option encoding, final IterV 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 null
* @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 IO> enumFileCharChunks(final File f, final Option encoding, final IterV 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 null
* @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 IO> enumFileChars(final File f, final Option encoding, final IterV i) {
return bracket(fileReader(f, encoding)
, Function.>vary(closeReader)
, partialApply2(IOFunctions.charChunkReader2(), i));
}
public static IO bufferedReader(final File f, final Option encoding) {
return IOFunctions.map(fileReader(f, encoding), new F() {
@Override
public BufferedReader f(final Reader a) {
return new BufferedReader(a);
}
});
}
public static IO fileReader(final File f, final Option encoding) {
return new IO() {
@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 IO bracket(final IO init, final F> fin, final F> body) {
return new IO() {
@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 IO unit(final A a) {
return new IO() {
@Override
public A run() throws IOException {
return a;
}
};
}
public static final IO ioUnit = unit(Unit.unit());
public static final IO lazy(final F0 p) {
return fromF(p);
}
public static final IO lazy(final F f) {
return () -> f.f(Unit.unit());
}
public static final SafeIO lazySafe(final F f) {
return () -> f.f(Unit.unit());
}
public static final SafeIO lazySafe(final F0 f) {
return f::f;
}
/**
* A function that feeds an iteratee with lines read from a {@link BufferedReader}.
*/
public static F, IO>>> lineReader() {
final F, Boolean> isDone =
new F, Boolean>() {
final F>, P1> done = constant(P.p(true));
final F, IterV>, P1> cont = constant(P.p(false));
@Override
public Boolean f(final IterV i) {
return i.fold(done, cont)._1();
}
};
return new F, IO>>>() {
@Override
public F, IO>> f(final BufferedReader r) {
return new F, IO>>() {
final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$
@Override
public IO> f(final IterV it) {
// use loop instead of recursion because of missing TCO
return new IO>() {
@Override
public IterV run() throws IOException {
IterV i = it;
while (!isDone.f(i)) {
final String s = r.readLine();
if (s == null) {
return i;
}
final Input input = Input.el(s);
final F, IterV>, P1>> cont = F1Functions.lazy(Function., IterV>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 F, IO>>> charChunkReader() {
final F, Boolean> isDone =
new F, Boolean>() {
final F>, P1> done = constant(P.p(true));
final F, IterV>, P1> cont = constant(P.p(false));
@Override
public Boolean f(final IterV i) {
return i.fold(done, cont)._1();
}
};
return new F, IO>>>() {
@Override
public F, IO>> f(final Reader r) {
return new F, IO>>() {
final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$
@Override
public IO> f(final IterV it) {
// use loop instead of recursion because of missing TCO
return new IO>() {
@Override
public IterV run() throws IOException {
IterV 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 input = Input.el(buffer);
final F, IterV>, P1>> cont =
F1Functions.lazy(Function., IterV>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 F, IO>>> charChunkReader2() {
final F, Boolean> isDone =
new F, Boolean>() {
final F>, P1> done = constant(P.p(true));
final F, IterV>, P1> cont = constant(P.p(false));
@Override
public Boolean f(final IterV i) {
return i.fold(done, cont)._1();
}
};
return new F, IO>>>() {
@Override
public F, IO>> f(final Reader r) {
return new F, IO>>() {
final F>, IterV> done = errorF("iteratee is done"); //$NON-NLS-1$
@Override
public IO> f(final IterV it) {
// use loop instead of recursion because of missing TCO
return new IO>() {
@Override
public IterV run() throws IOException {
IterV 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 input = Input.el(buffer[c]);
final F, IterV>, IterV> cont =
Function., IterV>apply(input);
i = i.fold(done, cont);
}
}
return i;
}
};
}
};
}
};
}
public static final IO map(final IO io, final F f) {
return new IO() {
@Override
public B run() throws IOException {
return f.f(io.run());
}
};
}
public static final IO bind(final IO io, final F> f) {
return new IO() {
@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 IO> sequence(List> list) {
F2, IO>, IO>> f2 = (io, ioList) ->
IOFunctions.bind(ioList, (xs) -> map(io, x -> List.cons(x, xs)));
return list.foldRight(f2, IOFunctions.unit(List.nil()));
}
public static IO> sequence(Stream> stream) {
F2>, IO, IO>> f2 = (ioList, io) ->
IOFunctions.bind(ioList, (xs) -> map(io, x -> Stream.cons(x, P.lazy(() -> xs))));
return stream.foldLeft(f2, IOFunctions.unit(Stream.nil()));
}
public static IO join(IO> io1) {
return bind(io1, io2 -> io2);
}
public static SafeIO> toSafeValidation(IO io) {
return () -> Try.f(() -> io.run())._1();
}
public static IO append(final IO io1, final IO io2) {
return () -> {
io1.run();
return io2.run();
};
}
public static IO left(final IO io1, final IO io2) {
return () -> {
A a = io1.run();
io2.run();
return a;
};
}
public static IO flatMap(final IO io, final F> 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 interactWhile(F condition, F transform) {
Stream> s1 = Stream.repeat(IOFunctions.stdinReadLine());
IO> io = sequenceWhile(s1, condition);
return () -> runSafe(io).foreach(s -> runSafe(stdoutPrintln(transform.f(s))));
}
public static IO> sequenceWhileEager(final Stream> stream, final F f) {
return new IO>() {
@Override
public Stream run() throws IOException {
boolean loop = true;
Stream> input = stream;
Stream 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 IO> sequenceWhile(final Stream> stream, final F f) {
return new IO>() {
@Override
public Stream run() throws IOException {
if (stream.isEmpty()) {
return Stream.nil();
} else {
IO io = stream.head();
A a = io.run();
if (!f.f(a)) {
return Stream.nil();
} else {
IO> io2 = sequenceWhile(stream.tail()._1(), f);
SafeIO> s3 = toSafe(() -> io2.run());
return Stream.cons(a, P.lazy(() -> s3.run()));
}
}
}
};
}
public static IO apply(IO io, IO> iof) {
return bind(iof, f -> map(io, a -> f.f(a)));
}
public static IO liftM2(IO ioa, IO iob, F2 f) {
return bind(ioa, a -> map(iob, b -> f.f(a, b)));
}
public static IO> replicateM(IO ioa, int n) {
return sequence(List.replicate(n, ioa));
}
public static IO>> 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 stdinReadLine() {
return () -> stdinBufferedReader.readLine();
}
public static IO stdoutPrintln(final String s) {
return () -> {
System.out.println(s);
return Unit.unit();
};
}
public static IO getContents() {
Stream> s = Stream.>repeat(() -> (int) stdinBufferedReader.read());
return IOFunctions.map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.map(i -> {
return (char) i.intValue();
})));
}
public static IO interact(F f) {
return bind(getContents(), ls1 -> {
LazyString ls2 = f.f(ls1);
return stdoutPrintln(ls2.toString());
});
}
}