Remix.run Logo
louthy 2 days ago

I think you're describing part of the bind function, which is part of the definition of the monad interface:

   a -> m b
But the full definition is:

   bind :: (a -> m b) -> m a -> m b
In C#, it would look like this:

   M<B> Bind(Func<A, M<B>> f, M<A> ma)
Assuming a future version of C# that supports higher-kinds that is.

In my language-ext library, I achieve a higher-kinded monad trait [1], like so:

    public interface Monad<M> : Applicative<M>, 
        where M : Monad<M>
    {
        static abstract K<M, B> Bind<A, B>(K<M, A> ma, Func<A, K<M, B>> f);
    }
Which is what the original comment is about. Most people in C# are not creating monads when they implement Bind or SelectMany for their type. They are simply making 'do-notation' work (LINQ). The monad abstraction isn't there until you define the Monad trait that allows the writing of any function constrained to said trait.

For example, a `When` function that runs the `then` monad when the `predicate` monad returns `true` for its bound value:

    public static K<M, Unit> When<M>(K<M, bool> predicate, K<M, Unit> then)
        where M : Monad<M> =>
        predicate.Bind(flag => flag ? then : M.Pure(unit));
This will work for any monad, `Option`, `List`, `Reader`, ... or anything that defines the trait.

So types like `Option` are just pure data-types. They become monads when the Monad trait is implemented for them. The monad trait can be implemented for data-types and function-types. Reader, for example, has the Monad trait implemented for the function: Func<E, A>

btw, monads also inherit behaviour from applicative and functor. The ability to lift pure values into the monad (a -> m a) is vital to making monads useful. This is `select` in LINQ, `pure` in Haskell's Applicative, and `Pure` in language-ext's Applicative [2].

[1] https://github.com/louthy/language-ext/blob/main/LanguageExt...

[2] https://github.com/louthy/language-ext/blob/main/LanguageExt...