HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

A monad in Java (FriendSpace and People)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
friendspacejavapeopleandmonad

Problem

After asking a similar question on Stack Overflow, I'm picking up on the answer there to improve my 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 and 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 {

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

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

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

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

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

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

        return next;
    }

    @SuppressWarnings("unchecked")
    public  FriendSpace pipeline(B value,
           FriendSpace.Function>... functions) {
        FriendSpace space = this;
        for (FriendSpace.Function> 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) cla

Solution

Well, lets find out by checking the monad laws.

-
(unit x) >>= f ≡ f x, which translates to the Java

new FriendSpace().unit(x).bind(..., f) ≡ f.apply(..., ...)


wait, why do bind and Function.apply take two arguments in your case?Note that the instance which you call a method on is an implicit argument. And why is the type of apply :: (B, MA) → MA incompatible with the proper usage A → M? It should look like

new Friendspace(x).bind(f) ≡  f.apply(x)


-
m >>= unit ≡ m, which translates to the Java

m.bind(..., new Function{
    public ... apply(..., ...){
        return new Friendspace().unit(argumentToApplyFunction);
    }
})
≡
m


Again, the arity of your methods is completely off. All the parts which don't make any sense have been marked with an ellipsis. Note that an equivalence relation is in no way required for monads, this notation is only chosen to emphasize that the two expressions should be equivalent for all purposes.

It should look like:

Friendspace m = ...;

m.bind(new Function>() {
    public Friendspace apply(A x){
        return new Friendspace(x);
    }
})
≡
m


-
(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) ), which should translate to

Friendspace m = ...;
Function> f = ...;
Function> g = ...;

m.bind(f).bind(g)
≡
m.bind(new Function>() {
    public Friendspace apply(A x) {
        return f.apply(x).bind(g);
    }
})


This is basically a requirement that bind composes the result of each function application in a sensible way.

Your class does not seem to be implementing the Monad pattern. A few pointers:

-
unit is basically just a constructor. It should either be a static method, or an actual constructor.

-
The bind operation takes a monad m :: M[A] and a function f :: A → M[B], and applies the function to each element contained in the monad. Each application returns a new monad, these are combined into a single one which then is the output of bind.

-
It is often more useful and simple to implement fmap :: (M[A], A → B) → M[B], as the transforming function doesn't need to know anything about the monad. If other languages like Haskell put a lot of emphasis on the bind, that's because bind is really important in its imperative “do-notation”, and because fmap can be easily derived from bind: fmap f m = m >>= (f . return), our in our Java notation:

public  Monad fmap(Function f) {
    return this.bind(new Function>() {
        public Monad apply(A x) {
            return new Monad(f.apply(x));
        }
    });
}


-
Mixing monads with mutability can make for very confusing results. Monads are a clever way to compose operations, so using mutable data structures really isn't necessary when using monads.

-
The Scala language has a well-designed immutable collection library. The classes are written in a way so that they can be used as monads. I suggest you play a bit with those to get a better feeling of monads.

I created an example of a List monad for you to look at. The List is slightly different from a normal monad, as it's additive, i.e. [a, b] + [c, d] = [a, b, c, d]. This makes implementing bind properly quite easy. I used a List for the example, because it's something you are probably familiar with, and the implementation of the linked list shouldn't detract too much from the monad implementation itself.

Code Snippets

new FriendSpace<...>().unit(x).bind(..., f) ≡ f.apply(..., ...)
new Friendspace<A>(x).bind(f) ≡  f.apply(x)
m.bind(..., new Function<..., ...>{
    public ... apply(..., ...){
        return new Friendspace<...>().unit(argumentToApplyFunction);
    }
})
≡
m
Friendspace<A> m = ...;

m.bind(new Function<A, Friendspace<A>>() {
    public Friendspace<A> apply(A x){
        return new Friendspace<A>(x);
    }
})
≡
m
Friendspace<A> m = ...;
Function<A, Friendspace<B>> f = ...;
Function<B, Friendspace<C>> g = ...;

m.bind(f).bind(g)
≡
m.bind(new Function<A, Friendspace<C>>() {
    public Friendspace<C> apply(A x) {
        return f.apply(x).bind(g);
    }
})

Context

StackExchange Code Review Q#41783, answer score: 8

Revisions (0)

No revisions yet.