That signature looks a little confused - you're saying "Monad" but you're talking about the specific case of Some and Maybe. The interface looks something like:
interface Monad<F<?>> {
fun flatMap<A, B>(argument: F<A>, function: A -> F<B>): F<B>
// also need pure/point here but ignore that for now
}
and then a specific implementation for e.g. Option looks like
singleton OptionMonad implements Monad<Option>
override fun flatMap<A, B>(argument: Option<A>, function: A -> Option<B>): Option<B> =
when(argument) {
is Some -> function(argument.value)
is None -> None
}
whereas you have implementations for other types in terms of those specific types too:
singleton FutureMonad implements Monad<Future> {
override fun flatMap<A, B>(argument: Future<A>, function: A -> Future<B>): Future<B> = ...
}
The tricky part is that the type parameter F is itself a parameterised type, so it's a slightly higher level of abstraction than most interfaces (and one that most languages don't support).
That return/pure/point followed by flatMap has to obey the Left Identity law in a monad for it to be a valid monad:
Monad(fa).pure(x).flatMap(f) valueEquals f(x)
The Right Identity law states that if we have a Monadic value and flatMap over the point/pure/return method we get a new monad that has value equal to the original monad:
m = Monad(fa)
Monad(fa).flatMap( (v) => Monad(fa).pure(v)) valueEquals m
Those are your unit tests that you have to perform on your monad instances. If they pass, your implementation is correct, and you can rely upon it.
These laws are so that the monad interface can be composed/extended correctly with other referentially transparent interfaces, like
Functor -provides map, the map must be associative, like flatMap);
Apply extends Functor - provides ap, or applyParallel that allows parallel mapping over collections that can be safely parallelized. ap has to be consistent with flatMap when an implementation has a monad instance -- that is if you map over a list in parallel, the list you get out should be the same as if you flatMapped over that same list using the same function, but wrapping List() around the result, which is sequential;
Applicative extends Apply - provides pure/point/return, which just constructs instances that are Applicatives from a given value;
Semigroup - provides combine/concat/++, allowing two items of the same type to be combined into a new value of that type; There may be many instances for a type of semigroup (Boolean has && and || for example);
Monoid extends Semigroup - provides empty, which is a value for a type when combined with another value for that type results in the other value (0 in integer addition, for example, an empty list in list concatenation, for another);
traverse - provides sequence, which flips the types of wrapped values (Maybe<List<T>> becomes List<Maybe<T>> when sequence is called), traverse<G<?>,F<?>, A, B>(ga: G<A>)(f: A => F<B>): F<G<B>> -- used to define sequence, foldRight, foldMap, foldLeft, etc;
And ApplicativeError - provides raiseError<F<?>, A,ERRORTYPE>(t: ERRORTYPE): F<A> and handleError<F<?>, A>(fa: F<A>)(f: ERRORTYPE => A) and catchNonFatal<F<?>, ERRORTYPE, A>(f: () => A):F<A> -- obviously these only work if your type has two members, one for success, and one for failure.