Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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;
        }
    };
share|improve this question

1 Answer 1

up vote 4 down vote accepted

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!

share|improve this answer
    
Thank you! I misunderstood bind in this pass, and took the perhaps understandable OO misstep of thinking bind mapped the monad back onto itself. But that's backwards, I see now. Thank you for your answer! –  Rob Y Feb 19 at 18:40
    
@RobY yep, it's tricky because it doesn't feel good to make up some false value of type b just to be polymorphic enough, which forces you to have that rejected flag because if b is an int, 0 could be a succesful value or the false value from rejection, so it's a path that isn't immediately obvious, but it's the only possible path in Java/C# style OOP. Sorry I posted this as comments and not an answer earlier, just saw it in passing on my phone and forgot to fix up an answer when I was at a computer. –  Jimmy Hoffa Feb 19 at 18:42
    
Glad you did! More points! :) –  Rob Y Feb 19 at 19:04

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.