Skip to content

Instantly share code, notes, and snippets.

@xpmatteo
Created November 2, 2023 18:57
Show Gist options
  • Save xpmatteo/070dbe79819afc04cbba6e3f415a6bda to your computer and use it in GitHub Desktop.
Save xpmatteo/070dbe79819afc04cbba6e3f415a6bda to your computer and use it in GitHub Desktop.
Recreating in vanilla Java the State monad example from https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State
package it.xpug.spike.monads.monad;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.function.Function;
import static it.xpug.spike.monads.monad.TurnstileTest.TurnstileOutput.*;
import static it.xpug.spike.monads.monad.TurnstileTest.TurnstileState.LOCKED;
import static it.xpug.spike.monads.monad.TurnstileTest.TurnstileState.UNLOCKED;
import static org.assertj.core.api.Assertions.assertThat;
record Pair<A, B>(A left, B right) {
public static <A,B> Pair<A, B> of(A left, B right) {
return new Pair<>(left, right);
}
}
// from https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State
public class TurnstileTest {
static class State<S, A> {
private Function<S, Pair<A, S>> runState;
public State(Function<S, Pair<A, S>> runState) {
this.runState = runState;
}
public static <S, A> State<S, A> unit(A a) {
return new State<>(s -> Pair.of(a, s));
}
public <B> State<S, B> bind(Function<A, State<S, B>> other) {
// apply both this.f and other.f and return the second value and the third state
Function<S, Pair<B, S>> newF = s0 -> {
// Running the first processor on s0
Pair<A, S> firstApply = this.runState.apply(s0);
A valueReturnedByFirst = firstApply.left();
S s1 = firstApply.right();
// Running the second processor on s1
State<S, B> second = other.apply(valueReturnedByFirst);
return second.runState.apply(s1);
};
return new State(newF);
}
}
enum TurnstileState {LOCKED, UNLOCKED};
enum TurnstileOutput {Thank, Open, Tut};
Function<TurnstileState, Pair<TurnstileOutput, TurnstileState>> coin = __ -> Pair.of(Thank, UNLOCKED);
Function<TurnstileState, Pair<TurnstileOutput, TurnstileState>> push = s ->
s == LOCKED ? Pair.of(Tut, LOCKED) : Pair.of(Open, LOCKED);
List<TurnstileOutput> monday(TurnstileState s0) {
var p1 = coin.apply(s0);
var p2 = push.apply(p1.right());
var p3 = push.apply(p2.right());
var p4 = coin.apply(p3.right());
var p5 = push.apply(p4.right());
return List.of(p1.left(), p2.left(), p3.left(), p4.left(), p5.left());
}
@Test
void withoutMonad() {
assertThat(monday(LOCKED)).isEqualTo(List.of(Thank, Open, Tut, Thank, Open));
}
State<TurnstileState, TurnstileOutput> coinS = new State(coin);
State<TurnstileState, TurnstileOutput> pushS = new State(push);
@Test
void runState() {
assertThat(coinS.runState.apply(LOCKED)).isEqualTo(Pair.of(Thank, UNLOCKED));
assertThat(pushS.runState.apply(LOCKED)).isEqualTo(Pair.of(Tut, LOCKED));
assertThat(pushS.runState.apply(UNLOCKED)).isEqualTo(Pair.of(Open, LOCKED));
}
List<TurnstileOutput> mondayS(TurnstileState s0) {
State<TurnstileState, List<TurnstileOutput>> boh =
coinS.bind(a1 ->
pushS.bind(a2 ->
pushS.bind(a3 ->
coinS.bind(a4 ->
pushS.bind(a5 ->
State.unit(List.of(a1, a2, a3, a4, a5)))))));
return boh.runState.apply(s0).left();
}
@Test
void withMonad() {
assertThat(mondayS(LOCKED)).isEqualTo(List.of(Thank, Open, Tut, Thank, Open));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment