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