Last active
December 18, 2017 02:30
-
-
Save tonymorris/06e9a700740cf6482aad6e57af0f64a8 to your computer and use it in GitHub Desktop.
Pure-functional terminal I/O in java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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