
fj.data.IOFunctions Maven / Gradle / Ivy
package fj.data;
import fj.F;
import fj.F0;
import fj.F1Functions;
import fj.F2;
import fj.Function;
import fj.P;
import fj.P1;
import fj.P2;
import fj.Try;
import fj.Unit;
import fj.data.Iteratee.Input;
import fj.data.Iteratee.IterV;
import fj.function.Try0;
import fj.function.Try1;
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 static fj.Bottom.errorF;
import static fj.Function.constant;
import static fj.Function.partialApply2;
/**
* 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() {
return LineReader::new;
}
private static class LineReader implements F, IO>> {
private final BufferedReader r;
private final F, Boolean> isDone = i -> i.fold(constant(P.p(true)), constant(P.p(false)))._1();
private final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$
private LineReader(BufferedReader r) {
this.r = r;
}
@Override
public IO> f(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() {
return CharChunkReader::new;
}
private static class CharChunkReader implements F, IO>> {
private final Reader r;
private final F, Boolean> isDone = i -> i.fold(constant(P.p(true)), constant(P.p(false)))._1();
private final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$
CharChunkReader(Reader r) {
this.r = r;
}
@Override
public IO> f(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() {
return CharChunkReader2::new;
}
private static class CharChunkReader2 implements F, IO>> {
private final Reader r;
private final F, Boolean> isDone = i -> i.fold(constant(P.p(true)), constant(P.p(false)))._1();
private final F>, IterV> done = errorF("iteratee is done"); //$NON-NLS-1$
CharChunkReader2(Reader r) {
this.r = r;
}
@Override
public IO> f(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());
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy