Skip to content

Instantly share code, notes, and snippets.

@tonymorris
Last active December 18, 2017 02:30
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tonymorris/06e9a700740cf6482aad6e57af0f64a8 to your computer and use it in GitHub Desktop.
Save tonymorris/06e9a700740cf6482aad6e57af0f64a8 to your computer and use it in GitHub Desktop.
Pure-functional terminal I/O in java
import java.util.function.Function;
import java.util.function.BiFunction;
import java.util.Scanner;
abstract class TerminalOperation<A> {
// this ensures that there are no subclasses of TerminalOperation
// outside of this class
private TerminalOperation() {
}
// There are exactly four subclasses of TerminalOperation:
// * WriteOut
// * WriteErr
// * ReadLine
// * Read
// Pattern-match the four cases.
public abstract <X> X fold(
BiFunction<String, A, X> writeOut
, BiFunction<String, A, X> writeErr
, Function<Function<String, A>, X> readLine
, Function<Function<Integer, A>, X> read
);
// WriteOut<A> is a pair of String and A
public final static class WriteOut<A> extends TerminalOperation<A> {
public final String str;
public final A value;
public WriteOut(final String str, final A value) {
this.str = str;
this.value = value;
}
public <X> X fold(
final BiFunction<String, A, X> writeOut
, final BiFunction<String, A, X> writeErr
, final Function<Function<String, A>, X> readLine
, final Function<Function<Integer, A>, X> read
) {
return writeOut.apply(str, value);
}
}
// WriteErr<A> is a pair of String and A
public final static class WriteErr<A> extends TerminalOperation<A> {
public final String str;
public final A value;
public WriteErr(final String str, final A value) {
this.str = str;
this.value = value;
}
public <X> X fold(
final BiFunction<String, A, X> writeOut
, final BiFunction<String, A, X> writeErr
, final Function<Function<String, A>, X> readLine
, final Function<Function<Integer, A>, X> read
) {
return writeErr.apply(str, value);
}
}
// ReadLine<A> is a function of String to A
public final static class ReadLine<A> extends TerminalOperation<A> {
public final Function<String, A> func;
public ReadLine(final Function<String, A> func) {
this.func = func;
}
public <X> X fold(
final BiFunction<String, A, X> writeOut
, final BiFunction<String, A, X> writeErr
, final Function<Function<String, A>, X> readLine
, final Function<Function<Integer, A>, X> read
) {
return readLine.apply(func);
}
}
// Read<A> is a function of Int to A
public final static class Read<A> extends TerminalOperation<A> {
public final Function<Integer, A> func;
public Read(final Function<Integer, A> func) {
this.func = func;
}
public <X> X fold(
final BiFunction<String, A, X> writeOut
, final BiFunction<String, A, X> writeErr
, final Function<Function<String, A>, X> readLine
, final Function<Function<Integer, A>, X> read
) {
return read.apply(func);
}
}
// TerminalOperation is a functor (can be mapped).
public final <B> TerminalOperation<B> map(final Function<A, B> f) {
return fold(
(s, a) -> new WriteOut<B>(s, f.apply(a))
, (s, a) -> new WriteErr<B>(s, f.apply(a))
, k -> new ReadLine<B>(f.compose(k))
, k -> new Read<B>(f.compose(k))
);
}
// Lift a TerminalOperation to a Terminal.
public final Terminal<A> lift() {
return new Terminal.More<A>(map(x -> new Terminal.Done<A>(x)));
}
}
abstract class Terminal<A> {
// this ensures that there are no subclasses of Terminal
// outside of this class
private Terminal() {
}
// There are exactly two subclasses of Terminal:
// * Done
// * More
// Pattern-match the two cases.
public abstract <X> X fold(
Function<A, X> done
, Function<TerminalOperation<Terminal<A>>, X> more
);
public final static class Done<A> extends Terminal<A> {
public final A value;
public Done(final A value) {
this.value = value;
}
public final <X> X fold(
final Function<A, X> done
, final Function<TerminalOperation<Terminal<A>>, X> more
) {
return done.apply(value);
}
}
public final static class More<A> extends Terminal<A> {
public final TerminalOperation<Terminal<A>> value;
public More(final TerminalOperation<Terminal<A>> value) {
this.value = value;
}
public final <X> X fold(
final Function<A, X> done
, final Function<TerminalOperation<Terminal<A>>, X> more
) {
return more.apply(value);
}
}
public final <B> Terminal<B> map(final Function<A, B> f) {
return fold(
a -> new Done<B>(f.apply(a))
, a -> new More<B>(a.map(k -> k.map(f)))
);
}
public final <B> Terminal<B> bind(final Function<A, Terminal<B>> f) {
return fold(
f
, a -> new More<B>(a.map(k -> k.bind(f)))
);
}
public static Terminal<Unit> writeOut(final String s) {
return new TerminalOperation.WriteOut<Unit>(s, Unit.unit).lift();
}
public static Terminal<Unit> writeErr(final String s) {
return new TerminalOperation.WriteErr<Unit>(s, Unit.unit).lift();
}
public static Terminal<String> readLine() {
return new TerminalOperation.ReadLine<String>(s -> s).lift();
}
public static Terminal<Integer> read() {
return new TerminalOperation.Read<Integer>(s -> s).lift();
}
}
final class TerminalInterpreter {
private TerminalInterpreter() {
}
/*
CAUTION: This function is unsafe.
It is the "end of the (Terminal) world" interpreter.
Use this function to run the final terminal program.
Ideally, this function would be hypothetical and unavailable
to the programmer API (i.e. implemented in its own runtime).
*/
public static <A> A interpret(final Terminal<A> z) {
final Scanner scan = new Scanner(System.in);
return z.fold(
a -> a
, a -> a.fold(
(s, t) -> {
System.out.println(s);
return interpret(t);
}
, (s, t) -> {
System.err.println(s);
return interpret(t);
}
, k -> {
final String line = scan.next();
return interpret(k.apply(line));
}
, k -> {
final Integer i = scan.nextInt();
return interpret(k.apply(i));
}
)
);
}
}
final class Unit {
private Unit() {
}
public static final Unit unit = new Unit();
}
public class PureIO {
public static class Previously {
static void writeOut(String s) { System.out.println(s); }
static void writeErr(String s) { System.err.println(s); }
static String readLine() { return (new Scanner(System.in)).next(); }
static Integer read() { return (new Scanner(System.in)).nextInt(); }
public static int program() {
writeOut("Hello, let us begin");
writeOut("Please enter your name");
String name = readLine();
writeOut("How old are you?");
String age = readLine();
writeOut("Okey dokey, ready to tell the world?");
writeOut("0. No");
writeOut("1. Yes");
int r = read();
writeOut("");
if(r == 0)
writeErr(name + " is modest");
else
writeOut(name + " is " + age + " years old");
return r - 48;
}
}
public static class Now {
public static Terminal<Integer> program() {
return
Terminal.writeOut("Hello, let us begin").bind(_1 ->
Terminal.writeOut("Please enter your name").bind(_2 ->
Terminal.readLine().bind(name ->
Terminal.writeOut("How old are you?").bind(_3 ->
Terminal.readLine().bind(age ->
Terminal.writeOut("Okey dokey, ready to tell the world?").bind(_4 ->
Terminal.writeOut("0. No").bind(_5 ->
Terminal.writeOut("1. Yes").bind(_6 ->
Terminal.read().bind(r ->
Terminal.writeOut("").bind(_7 ->
(r == 0
? Terminal.writeErr(name + " is modest")
: Terminal.writeOut(name + " is " + age + " years old")).map(_8 ->
r - 48)))))))))));
}
}
public static void main(String[] args) {
final Terminal<Integer> p = Now.program();
final int i = TerminalInterpreter.interpret(p);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment