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.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 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 = IOFunctions::closeReader;
/**
* 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 () -> {
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 map(fileReader(f, encoding), BufferedReader::new);
}
public static IO fileReader(final File f, final Option encoding) {
return () -> {
final FileInputStream fis = new FileInputStream(f);
return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some());
};
}
public static IO bracket(final IO init, final F> fin, final F> body) {
return () -> {
final A a = init.run();
try(Closeable finAsCloseable = fin.f(a)::run) {
return body.f(a).run();
}
};
}
public static IO unit(final A a) {
return () -> a;
}
public static final IO ioUnit = unit(Unit.unit());
public static IO lazy(final F0 p) {
return fromF(p);
}
public static IO lazy(final F f) {
return () -> f.f(Unit.unit());
}
public static SafeIO lazySafe(final F f) {
return () -> f.f(Unit.unit());
}
public static 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 r -> 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 () -> {
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.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 r -> 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 () -> {
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.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 r -> 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 () -> {
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 (char c : buffer) {
final Input input = Input.el(c);
final F, IterV>, IterV> cont =
Function.apply(input);
i = i.fold(done, cont);
}
}
return i;
};
}
};
}
public static IO map(final IO io, final F f) {
return () -> f.f(io.run());
}
public static IO as(final IO io, final B b) {
return map(io, ignored -> b);
}
public static IO voided(final IO io) {
return as(io, Unit.unit());
}
public static IO bind(final IO io, final F> f) {
return () -> f.f(io.run()).run();
}
public static IO when(final Boolean b, final IO io) {
return b ? io : ioUnit;
}
public static IO unless(final Boolean b, final IO io) {
return when(!b, io);
}
/**
* 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) ->
bind(ioList, (xs) -> map(io, x -> List.cons(x, xs)));
return list.foldRight(f2, unit(List.nil()));
}
public static IO> sequence(Stream> stream) {
F2>, IO, IO>> f2 = (ioList, io) ->
bind(ioList, (xs) -> map(io, x -> Stream.cons(x, () -> xs)));
return stream.foldLeft(f2, 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(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 () -> {
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 () -> {
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, s3::run);
}
}
};
}
public static IO apply(IO io, IO> iof) {
return bind(iof, f -> map(io, f));
}
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((Try1) BufferedReader::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 stdoutPrint(final String s) {
return () -> {
System.out.print(s);
return Unit.unit();
};
}
public static IO getContents() {
Stream> s = Stream.repeat(() -> stdinBufferedReader.read());
return map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.map(i -> (char) i.intValue())));
}
public static IO interact(F f) {
return bind(getContents(), ls1 -> {
LazyString ls2 = f.f(ls1);
return stdoutPrintln(ls2.toString());
});
}
}