←back to thread

A list is a monad

(alexyorke.github.io)
153 points polygot | 6 comments | | HN request time: 0s | source | bottom
Show context
brooke2k ◴[] No.44445948[source]
As far as monad tutorials go, this one seems quite good. I like the categorization of monads between "containers" and "recipes".

However, I personally think that monad tutorials tend to give people the wrong impression and leave them more confused than they were before, because they focus on the wrong thing.

A monad is not a complex concept, at all. IMO a more useful way to present the topic would be with one separate lesson for every common monad instance. Start with Maybe, then IO, then maybe State and List, and so on... because ultimately, every instance of a Monad works very differently. That's why the pattern is so useful in the first place, because it applies to so many places. (Note: this is a criticism of monad tutorials in general, not this one in particular, which seems to do a decent job on this front).

In my experience, people new to Haskell focus way too much on getting the "a-ha" moment for monads in general, when really you want a bunch of separate "a-ha" moments as you realize how each instance of a monad takes advantage of the pattern differently.

I also tend to think that monads are best demonstrated in Haskell rather than in other languages, if only because the notation is so much less clunky. That may just be me though. (EDIT: well, also because almost no other languages have typeclasses, so you have to approximate it with interfaces/traits/etc)

Also FYI: in part 2, the code examples have extra newlines in between every line, which makes it hard to read (I'm on firefox, if that matters).

replies(15): >>44446327 #>>44446377 #>>44446564 #>>44446988 #>>44447713 #>>44448118 #>>44448413 #>>44449093 #>>44449627 #>>44449895 #>>44450873 #>>44450887 #>>44451012 #>>44451851 #>>44458827 #
pdhborges ◴[] No.44446327[source]
If all monad instances work differently what is the value of the Monad interface? What kind of usefull generic code can one write against the Monad interface.

Related: https://buttondown.com/j2kun/archive/weak-and-strong-algebra...

replies(11): >>44446453 #>>44446472 #>>44446556 #>>44446586 #>>44446781 #>>44446882 #>>44447360 #>>44448151 #>>44448170 #>>44450818 #>>44462125 #
moomin ◴[] No.44446472[source]
Your basic problem is that your programming language can’t express the concept cleanly. You need what’s called “Higher-Kinded Types”.

To give you a concrete example, in C#

Func<A, B>, List<A> -> List<B>

Func<A, B>, Task<A> -> Task<B>

Func<A, B>, Func<C, A> -> Func<C, B>

Can’t be expressed using a generalisation. But in Haskell, you can write

(Functor F) => Func<A,B>, F<A> -> F<B>

One of the biggest things that makes monads hard to understand is that the type systems of most languages can’t represent them. Annoying, that includes the “typeless” ones.

replies(3): >>44447090 #>>44448888 #>>44450834 #
1. jameshart ◴[] No.44450834{3}[source]
I'm sorry, I'm not sure I understand entirely what you are trying to express by

  Func<A, B>, List<A> -> List<B>
That said, in C#, you can write:

  List<A> listA;
  Task<A> taskA;
  Func<A, B> func;
  
  List<B> listB = from i in listA select func(i);
  Task<B> taskB = from t in taskA select func(t);
And if it can resolve a method on List<T> called 'Select' that takes a Func<T, S> that returns a List<S>, and a method on Task<T> called 'Select' that takes a Func<T, S> that returns a Task<S> this will compile.

In other words, I kind of think that Select methods (which can be Select extension methods, of course) amount to functors in C#?

replies(1): >>44451109 #
2. instig007 ◴[] No.44451109[source]
now write a single function that performs

  from x in xs select fn(x)
and define a signature for it where `xs` and `fn` are the only input arguments, so that it accepts both `listB` and `taskB` without a compilation error.
replies(2): >>44451155 #>>44453266 #
3. jameshart ◴[] No.44451155[source]
Right, ‘some type that has a Select(Func<T, S>) method available’ is not a thing you can express in C# generic constraints.

But I don’t need a function that does that, the language has syntax for it - I can just do that wherever I need it.

replies(2): >>44451194 #>>44452082 #
4. instig007 ◴[] No.44451194{3}[source]
> Right, ‘some type that has a Select(Func<T, S>) method available’

not just Select(Func<T, S>), but a Select(Func<T, S>) that preserves its original contextual instance of Select and doesn't leak into a different instance with Select.

> But I don’t need a function that does that

you don't need it yet ;)

5. moomin ◴[] No.44452082{3}[source]
Yes, but you can’t write something that’s generic over “things that support Select” because that’s not expressible via the type system.

So you can’t write a function, then get a version that works on lists, then a version that works on tasks, then a version that works on nullables, then get a version that works on parsers. One if the big secrets on monads is that Haskell users don’t spend very much time thinking about them, while people without monads have to think about them all the time.

6. gpderetta ◴[] No.44453266[source]
In C++:

    template<template<class> class Container, class T, invocable<T> Fn>
    Container<invoke_result_t<Fn, T> > map(Container<T> container, Fn fn) {
      return to<Container>(transform(container,  fn));
    }
(Although idiomatically you wouldn't write the code this way).

Whether that counts as HK types, I'll leave it to others to discuss.