Skip to content

Instantly share code, notes, and snippets.

@danhyun
Last active May 27, 2024 17:38
Show Gist options
  • Save danhyun/fda27d5682b7dbed151b to your computer and use it in GitHub Desktop.
Save danhyun/fda27d5682b7dbed151b to your computer and use it in GitHub Desktop.
`Reader` and `TryReader` monads as demonstrated by Mario Fusco (@mariofusco) Devoxx 2015
public class Reader<R, A> {
private final Function<R, A> run;
public Reader(Function<R, A> run) { this.run = run; }
public <B> Reader<R, B> map(Function<A, B> f) {
return new Reader<>( r -> f.apply((apply(r))) );
}
public <B> Reader<R, B> flatMap(Function<A, Reader<R, B>> f) {
return new Reader<>( r -> f.apply(apply(r)).apply(r) );
}
public A apply(R r) {
return run.apply(r);
}
}
public class TryReader<R, A> {
private final Function<R, Try<A>> run;
public TryReader(Function<R, Try<A>> run) { this.run = run; }
public <B> TryReader<R, B> map(Function<A, B> f) {
return new TryReader<>(r -> apply(r).map(f::apply));
}
public <B> TryReader<R, B> flatMap(Function<A, TryReader<R, B>> f) {
return new TryReader<>(r -> apply(r).flatMap(a -> f.apply(a).apply(r)));
}
public <B> TryReader<R, B> mapReader(Function<A, Reader<R, B>> f) {
return new TryReader<>(r -> apply(r).map( a -> f.apply(a).apply(r) ) );
}
public Try<A> apply(R r) { return run.apply(r); }
}
@danhyun
Copy link
Author

danhyun commented Nov 29, 2015

Example using Reader and TryReader monads

import javaslang.control.Failure;
import javaslang.control.Success;
import javaslang.control.Try;

import java.math.BigDecimal;
import java.util.Objects;
import java.util.function.Function;

public interface BankConnection {

}

public class Balance {
    public final BigDecimal amount;
    public Balance(BigDecimal amount) {
        this.amount = amount;
    }
}

public class InsufficientBalanceError extends Exception {}

public class Account {
    public final String owner;
    public final String number;
    public final Balance balance;

    public Account(String owner, String number, Balance balance) {
        this.owner = owner;
        this.number = number;
        this.balance = balance;
    }

    public Account credit(BigDecimal value) {
        return new Account(owner, number, new Balance(value));
    }

    public Try<Account> debit(BigDecimal value) {
        if (balance.amount.compareTo(value) < 0) {
            return new Failure<>(new InsufficientBalanceError());
        }
        return new Success<>(new Account(
                owner, number, new Balance(balance.amount.subtract(value))));
    }
}

public interface BankService {
    static TryReader<BankConnection, Account> open(String owner, String number, BigDecimal balance) {
        if (balance.compareTo(BigDecimal.ZERO) < 0) {
            return new TryReader<>(bc -> new Failure<>(new InsufficientBalanceError()));
        }
        return new TryReader<>(bc -> new Success<>(new Account(owner, number, new Balance(balance))));
    }

    static Reader<BankConnection, Account> credit(Account account, BigDecimal value) {
        return new Reader<>(bc -> new Account(account.owner, account.number,
                new Balance(account.balance.amount.add(value))));
    }

    static TryReader<BankConnection, Account> debit(Account account, BigDecimal value) {
        if (account.balance.amount.compareTo(value) < 0) {
            return new TryReader<>(bc -> new Failure<>(new InsufficientBalanceError()));
        }
        return new TryReader<>(bc -> new Success<>(new Account(account.owner, account.number,
                new Balance(account.balance.amount.subtract(value)))));
    }

    static void main(String[] args) {
        TryReader<BankConnection, Account> reader = open("Alice", "123", new BigDecimal(100.0))
                .mapReader(a -> credit(a, new BigDecimal(200.0)))
                .mapReader(a -> credit(a, new BigDecimal(300.0)))
                .flatMap(a -> debit(a, new BigDecimal(400)));

        Try<Account> account = reader.apply(new BankConnection(){});

        assert Objects.equals(account.get().owner, "Alice");
        assert Objects.equals(account.get().number, "123");
        assert account.get().balance.amount.equals(new BigDecimal(200));
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment