Question

My first try was on stackoverflow. I'm picking up on the answer there to improve my monad: StackOverflow - Is this a monad in Java?

My goal is to write an example of a monad. I'm not trying to solve the general case, just come up with one to see how it works. If I'm right, then I really like this pattern. I've already used it to rework some code I'm working on. So I hope it's right.

I was pretty strict about the signatures & not taking shortcuts that OO allows.

The (contrived) example is to provide semantics to represent a Friend relationship between two users. If there's an error, or the friendship is blocked, then processing stops. I have sample code that "lifts" Boolean, String and a custom Friend class into the monadic space, so I think that' a sign I'm on the right track. I've included the sample code lifting String into the monadic space.

My question is, did I get the monad implementation right? Please call out the places where I jumped the rails!

public class FriendSpace<A> {

    public boolean rejected = false;
    public String errors = "";
    public A original;

    public interface Function<B, MA> {
        MA apply(B b, MA a);
    }

    public FriendSpace<A> unit(A a) {
        FriendSpace<A> that = new FriendSpace<A>();
        that.original = a;
        return that;
    }

    public <B> FriendSpace<A> bind(B b, Function<B, FriendSpace<A>> f) {
        if (! errors.isEmpty()) {
            // we have errors; skip the rest
            return this;
        }

        if (rejected) {
            // No means no
            return this;
        }

        FriendSpace<A> next = f.apply(b, this);

        return next;
    }

    @SuppressWarnings("unchecked")
    public <B> FriendSpace<A> pipeline(B value,
           FriendSpace.Function<B, FriendSpace<A>>... functions) {
        FriendSpace<A> space = this;
        for (FriendSpace.Function<B, FriendSpace<A>> f : functions) {
            space = space.bind(value, f);
        }
        return space;
    }

    // toString omitted to save space
}

And here's an example where the (arbitrary) input is a People class, and the (arbitrary) class representing the friendship state is String

public class People {

    public People(String from, String to) {
        this.from = from;
        this.to = to;
    }

    public String from;
    public String to;
}

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        FriendSpace<String> proto = new FriendSpace<String>();

        People people0 = new People("Bob", "Fred");
        FriendSpace<String> space0 = proto.unit("");
        FriendSpace<String> friends0 = space0.pipeline(people0, VERIFY, INVITE, ACCEPT);
        System.err.println(friends0);

        People people1 = new People("Bob", "Jenny");
        FriendSpace<String> space1 = proto.unit("");
        FriendSpace<String> friends1 = space1.pipeline(people1, VERIFY, INVITE, ACCEPT);
        System.err.println(friends1);

        People people2 = new People("Fred", "Jenny");
        FriendSpace<String> space2 = proto.unit("");
        FriendSpace<String> friends2 = space2.pipeline(people2, VERIFY, INVITE, BLOCK);
        System.err.println(friends2);

        People people3 = new People("Bob", "Tom");
        FriendSpace<String> space3 = proto.unit("");
        FriendSpace<String> friends3 = space3.pipeline(people3, VERIFY, INVITE, ACCEPT);
        System.err.println(friends3);
    }

    public interface StringFunction extends FriendSpace.Function<People, FriendSpace<String>> {
    }

    static StringFunction VERIFY = new StringFunction() {
        public FriendSpace<String> apply(People from, FriendSpace<String> to) {
            String KNOWN_USERS = "Bob Fred Jenny";

            if (! KNOWN_USERS.contains(from.from)) {
                to.errors += "Unknown from: " + from.from;
            }

            if (! KNOWN_USERS.contains(from.to)) {
                to.errors += "Unknown to: " + from.to;
            }

            return to;
        }
    };

    static StringFunction INVITE = new StringFunction() {
        public FriendSpace<String> apply(People from, FriendSpace<String> to) {
            // Jenny has blocked Bob
            if ("Jenny".equals(from.to) && "Bob".equals(from.from)) {
                to.errors = "Jenny blocked Bob";
            }
            return to;
        }
    };

    static StringFunction ACCEPT = new StringFunction() {
        public FriendSpace<String> apply(People from, FriendSpace<String> to) {
            // Good to go!
            to.original = "YES";
            return to;
        }
    };

    static StringFunction BLOCK = new StringFunction() {
        public FriendSpace<String> apply(People from, FriendSpace<String> to) {
            to.original = "BLOCK";
            to.rejected = true;
            return to;
        }
    };
Was it helpful?

Solution

The place Java and C# fall down in implementing monads is if you bind a function a -> m b where a != b you can't return this - you need to construct a default b to create the m b where in Haskell Nothing is a valid m b construction as well as Left anything can supplant Right anything.

You have an Either monad here which is what we OO folks prefer over Maybe because it allows us to trap some information with the issue explicitly (FP folks tend to prefer the implicit and more concise Nothing from the Maybe monad).

Fix your return this to meet the requisite polymorphism so it can take an a -> m b and return an m b even in the failure case. You'll need to return new FriendState(something else) because this may be a FriendState<a> but your bind needs to return a FriendState<b>, right now failure only allows returning FriendState<a> which only works when you bind functions that go from a -> FriendState<a>.

I know that's the tricky part as a C# guy who's done variations on monads in C# myself, it's hard to get the amount of polymorphism ADTs give you. This is a very good first crack!

Licensed under: CC-BY-SA with attribution
scroll top