Question

Before I go around claiming I've nailed it, I wanted to ask. Don't hold back, please, I'd like to experience any criticism here first.

Since Java lacks the flexibility the dynamic languages have in passing arbitrary arguments, I compensated by bundling all possible inputs into one class (People), which forms the input space. The functions map that to the output space (Friends) with the help of the monad.

I'm not trying to solve the general case, just come up with an example to see if I understand the design pattern.

The monad (if it is such) enforces these rules: once an invitation is rejected, or there is an error, there is no further processing.

Sorry for the length. It is Java after all. I've taken some shortcuts with convention to save space.

Is this actually an example of a monad? (Encapsulated in the Friends class)

public class Sample {
    public static void main(String[] args) {
        People people0 = new People("Bob", "Fred");
        Friends friends0 = Friends.pipeline(people0, ToFromFunction.INVITE, ToFromFunction.ACCEPT);
        System.err.println(friends0);

        People people1 = new People("Bob", "Jenny");
        Friends friends1 = Friends.pipeline(people1, ToFromFunction.INVITE, ToFromFunction.ACCEPT);
        System.err.println(friends1);

        People people2 = new People("Bob", "Fred");
        Friends friends2 = Friends.pipeline(people2, ToFromFunction.INVITE, ToFromFunction.BLOCK);
        System.err.println(friends2);
    }
}

/** this is the space of all inputs */
public class People {
    public People(String from, String to) {
        this.from = from;
        this.to = to;
    }

    public String from;
    public String to;
}

/** this is the output space, and the monad (?) */
public class Friends {

    public boolean friends = false;
    public boolean rejected = false;
    public String errors = "";

    public static Friends unit(People from) {
        return new Friends();
    }

    public Friends bind(ToFromFunction f, People from) {
        if (! errors.isEmpty()) {
            // we have errors; skip the rest
            return this;
        }

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

        return f.act(from, this);
    }

    public static Friends pipeline(People from, ToFromFunction... functions) {
        Friends result = Friends.unit(from);
        for (ToFromFunction f : functions) {
            result = result.bind(f, from);
        }
        return result;
    }
}

/** functions from People to Friends */
public interface ToFromFunction {

    Friends act(People from, Friends to);

    ToFromFunction INVITE = new ToFromFunction() {
        public Friends act(People from, Friends to) {
            // Jenny has blocked Bob
            if ("Jenny".equals(from.to) && "Bob".equals(from.from)) {
                to.errors = "Jenny blocked Bob";
            }
            return to;
        }
    };

    ToFromFunction ACCEPT = new ToFromFunction() {
        public Friends act(People from, Friends to) {
            // Good to go!
            to.friends = true;
            return to;
        }
    };

    ToFromFunction BLOCK = new ToFromFunction() {
        public Friends act(People from, Friends to) {
            to.friends = false;
            to.rejected = true;
            return to;
        }
    };
}
Was it helpful?

Solution

Monads are Types, that implement two functions with a fixed signature: unit and bind.

In Haskell notation:

unit :: a -> m a

bind :: m a -> (a -> m b) -> m b

unit wraps an object from type a into an m of type a. The type of a must be arbitrary. Implementation of bind can be anything (but it must satisfy the monadic laws).

Let's try to convert your example into Haskell syntax:

People is just a tuple:

type People = (String, String)

The type of Friends is a tripel of two booleans and a String.

If we use these types, than your Friends.unit method is something like this:

unit_friends :: People -> Friends
unit_friends _ = (false, false)

That means, unit_friends is discarding is parameter and just returning a new instance of Friends. This is the wrong type signature for unit. Instead, unit should have this type signature:

unit_friends :: a -> Friends a

In Java, this should look something similar to:

public static Friends<T> unit(T from) {
    // return something of type Friends<T>
}

Your bind function takes a function of type ToFromFunction and an object of type People and returns something of type Friends:

bind_friends :: ToFromFunction -> People -> Friends

Let's replace ToFromFunction with

type ToFromFunction = People -> Friends -> Friends

because that's the type signature of act.

bind_friends :: (People -> Friends -> Friends) -> People -> Friends

Let's flip the arguments of bind_friends, because the lambda function should be the second parameter:

bind_friends :: People -> (People -> Friends -> Friends) -> Friends

But it shoud have this type signature:

bind_friends :: Friends a -> (a -> Friends b) -> Friends b

Your types of unit and bind don't match that of a real monad, but it is kind of close.

Let's forget for a moment, that we need an arbitrary type a. The first parameter of bind_friends must be of type Friends, not of type People, because bind should lift ToFromFunction to the Friends monad.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top