Remix.run Logo
epolanski 3 days ago

Small OT but part of me dies when data types that respect some laws are just labeled monads.

Nobody calls an array a monad, even though an array admits a monad instance.

Option, Result, Array, Either, FunkyFoo, whatever you want are just data types.

They only become monads when combined with some functions (map, bind, apply, flatmap), and that combination of things respects a set of law.

But calling a data type alone a monad has done nothing but overcomplicate the whole matter for decades.

mrkeen 3 days ago | parent | next [-]

Sure, but this article's Result implements Ok(), Map(), and Bind()

pjc50 3 days ago | parent | prev [-]

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