Last active
April 2, 2022 16:23
-
-
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#
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; | |
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