Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Can you expand on the connection between linq and monads? I have a friend that's a long time .net developer and I've been trying to explain some of the benefits of FP, but we lack a common language to discuss the concepts. Also, doesn't the lack of higher kinded types preclude use of monads in c#?



Monads in C# [1]

Arr<A>, Lst<A>, Seq<A>, Set<A>, HashSet<A>, Que<A>, Stck<A>, Option<A>, OptionAsync<A>, Either<A>, EitherAsync<A>, Try<A>, TryAsync<A>, TryOption<A>, TryOptionAsync<A>, Reader<Env, A>, Resource<A>, Writer<MonoidW, W, T>, State<S, A>, Task<T> (extensions), Nullable<T> (extensions), Validation<Fail, Success>, Validation<MonoidFail, Fail, Success> + Monad transformer stack

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


If you implement your own type that includes the right functions with the right signatures, you can incorporate expressions with that type into LINQ expressions. Crucially, SelectMany (most notably, IEnumerable.SelectMany, but the compiler just needs a function of the right name and signature and does not care about the interface) is the monadic bind. I believe Eric Lippert expands on this in his blog series on monads: https://ericlippert.com/2013/02/21/monads-part-one/


Yes, lack of the higher kind polymorphism makes it harder to express these abstract types universally, but only concrete implementations with Select and SelectMany.

SelectMany is similar to a monadic bind, with an extra selector parameter in its signature, you can achieve the following expression:

  from one in Maybe.Of(1)
  from two in MaybeAddOne(one)
  Select two
The first line will become a Select call and the second line will become a SelectMany call.

Another thing is async/await is pretty much similar to LinQ expressions, but it's hardcoded with the type Task.


> Yes, lack of the higher kind polymorphism makes it harder to express these abstract types universally

Here's a way of doing ad-hoc polymorphism to implement a more general monad in C#. It's limited in that the return type for Bind can be any monad (although it still type checks). There are a few other issues (not least it's ugly as hell). But, it does allow for building of very general monadic operations (along with other ad-hoc polymorphic types).

    // Typeclass (ish)
    public interface Monad<MA, A>
    {
        MA Return(A x);

        MA Fail(object error = null);

        MB Bind<MonadB, MB, B>(MA ma, Func<A, MB> f) 
            where MonadB : struct, Monad<MB, B>;
    }

    // Class instance of Monad for Option
    public struct MOption<A> : Monad<Option<A>, A>
    {
        public MB Bind<MonadB, MB, B>(Option<A> ma, Func<A, MB> f)
            where MonadB : struct, Monad<MB, B> =>
                ma is Some<A> x
                    ? f(x.Value)
                    : default(MonadB).Fail();

        public Option<A> Return(A x) =>
            new Some<A>(x);

        public Option<A> Fail(object error = null) =>
            new None<A>();
    }

    // Option 'discriminated union'
    public interface Option<A>
    {
    }

    public class Some<A> : Option<A>
    {
        public readonly A Value;
        public Some(A value) => Value = value;
    }

    public class None<A> : Option<A>
    {
        public None() { }
    }

    // Testing adding any two M<int> types together.  Doesn't need to just work with
    // ints, it's just a simple example.
    public class Test
    {
        public void Test1()
        {
            // Some 200
            var r1 = AddAnyMonads<MOption<int>, Option<int>>(new Some<int>(100), new Some<int>(100));

            // None
            var r2 = AddAnyMonads<MOption<int>, Option<int>>(new Some<int>(100), new None<int>());
        }

        public MInt AddAnyMonads<MonadInt, MInt>(MInt ma, MInt mb)
            where MonadInt : struct, Monad<MInt, int> =>
            default(MonadInt).Bind<MonadInt, MInt, int>(ma, x =>
            default(MonadInt).Bind<MonadInt, MInt, int>(mb, y =>
            default(MonadInt).Return(x + y)));
    }
I have a more complete example in language-ext [1] which unifies monads that take inputs (like Reader and State) and produce output (like Writer) with the more simple monads like Option.

[1] https://github.com/louthy/language-ext/blob/master/LanguageE...


Link is syntactic sugar for monads, just like do notation in Haskell. When you write 'from a in selector select a.x' that is translated to 'selector.then(a => return(a.x))' where 'then' is the C# equivalent of 'bind' and 'return' the same as Haskell's 'return'. That means that line is the same as 'do {a <- selector; pure (x a)}' in Haskell.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: