Skip to content

Instantly share code, notes, and snippets.

@tonymorris
Last active April 2, 2022 16:23
Show Gist options
  • Save tonymorris/7817335 to your computer and use it in GitHub Desktop.
Save tonymorris/7817335 to your computer and use it in GitHub Desktop.
A demonstration of pure-functional I/O using the free monad in C#
import java.util.function.Function;
import java.util.function.BiFunction;
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))
);
}
}
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
public final static class Done<A> extends Terminal<A> {
public final A value;
public Done(final A value) {
this.value = value;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment