Functor<B> fmap<B>(Func<A, B> f);
interface Applicative<A> : Functor<A> {
Applicative<A> pure(A value);
Applicative<B> apply<B>(Applicative<Func<A, B>> f);
interface Monad<A> : Applicative<A> {
Monad<A> mreturn(A value) => (Monad<A>)pure(value);
Monad<B> bind<B>(Func<A, Monad<B>> f);
Monad<B> compose<B>(Monad<B> mb) => bind(x => mb);
abstract class Maybe<A> : Monad<A> {
public static Maybe<A> pure(A value) => new Just<A>(value);
public static Maybe<A> mreturn(A value) => pure(value);
Applicative<A> Applicative<A>.pure(A value) => Maybe<A>.pure(value);
Functor<B> Functor<A>.fmap<B>(Func<A,B> f)
Just<A> ma => new Just<B>(f(ma.Value)),
Applicative<B> Applicative<A>.apply<B>(Applicative<Func<A,B>> mf)
Just<Func<A, B>> f => fmap(f.Value),
Monad<B> Monad<A>.bind<B>(Func<A, Monad<B>> f)
Just<A> ma => f(ma.Value),
public Maybe<B> fmap<B>(Func<A,B> f) => (Maybe<B>)(this as Functor<A>).fmap(f);
public Maybe<B> apply<B>(Maybe<Func<A,B>> f) => (Maybe<B>)(this as Applicative<A>).apply((Applicative<Func<A,B>>)f);
public Maybe<B> bind<B>(Func<A, Maybe<B>> f) => (Maybe<B>)(this as Monad<A>).bind((Func<A, Monad<B>>)f);
public Maybe<B> compose<B>(Maybe<B> mb) => (Maybe<B>)(this as Monad<A>).compose(mb);
class Just<A> : Maybe<A> {
public A Value { get; private set; }
public Just(A value) { Value = value; }
public override string ToString() => $"Just {Value}";
class Nothing<A> : Maybe<A> {
public override string ToString() => "Nothing";
public static void Main (string[] args) {
var str1 = Maybe<string>.pure("test");
var mf = Maybe<Func<string, string>>.pure(x => $"{x} value");
var mg = Maybe<Func<string, string>>.pure(x => x.ToUpper());
var res = str1.apply(mf).apply(mg);
var n = new Nothing<Func<string, int>>();
Console.WriteLine(res.apply(n));
var str2 = new Nothing<string>();
Console.WriteLine(str2.apply(mf));
Func<string, Maybe<string>> f = x => x != null ? Maybe<string>.pure(x) : new Nothing<string>();
Func<string, Maybe<int>> toInt = s => int.TryParse(s, out var i) ? Maybe<int>.pure(i) : new Nothing<int>();
Console.WriteLine(str1.bind(f));
Console.WriteLine(str2.bind(f));
Console.WriteLine(str1.bind(toInt));
Console.WriteLine(Maybe<string>.pure("10").bind(toInt));