Remix.run Logo
pjc50 3 days ago

I was wondering about that. "Monad" is a mildly obfuscatory term for "function that takes one argument and returns one value of the same type", and a List is not a function.

mrkeen 2 days ago | parent | next [-]

Where did you get that definition?

"function that takes one argument and returns one value of the same type" is the identity function.

epolanski 2 days ago | parent [-]

Identity function returns the same _value_.

If it's only the same _type_, but the value is not the same, then it's an endomorphism. The function definitions look the same `a -> a`.

string reversal, integer negation or toUpperCase are classical examples of endomorphisms.

Identity is a specific case of endomorphism.

mrkeen 2 days ago | parent [-]

string reversal, integer negation or toUpperCase are classical examples of functions which will not compile as `a -> a`

The function which will compile as `a -> a` is the identity function.

epolanski 2 days ago | parent [-]

That's correct, but I'm not sure how it relates to my comment as I said that `a -> a` is just an endomorphism.

identity, uppercase or negate are all endomorphisms, with identity being the only generic one.

louthy 2 days ago | parent | prev [-]

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...