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

Monads are effectively pipes; the monad controls how data flows through the functions you put into the monad, but the functions individually are like individual programs in a pipe.


I'm not sure I agree with this. Function composition is more directly comparable to pipes, whereas I tend to think of monads as collapsing structure (i.e. `join :: m (m a) -> m a`)


Some of the most common monads (especially lists and other collection-like things) feel very much like pipes.

    list.select { |x|  x.foo > 10 }.map { |x| x.bar }...
Etc.

I wouldn't make the same argument about the IO monad, which I think more in terms of a functional program which evaluates to an imperative program. But most monads are not like the IO monad, in my experience at least.


> list.select { |x| x.foo > 10 }.map { |x| x.bar }...

Forgive me if I'm misreading this syntax, but to me this looks like plain old function composition: a call to `select` (I assume that's like Haskell's `filter`?) composed with a call to `map`. No monad in sight.

As I mentioned, monads are more about collapsing structure. In the case of lists this could be done with `concat` (which is the list implementation of monad's `join`) or `concatMap` (which is the list implementation of monad's `bind` AKA `>>=`).


Nope, it's not. It's Ruby, and the list could be an eager iterator, an actual list, a lazy iterator, a Mabye (though it would be clumsy in Ruby), etc.

And monads are not "more about collapsing structure". They are just a design pattern that follows a handful of laws. It seems like you're mistaking their usefulness in Haskell for what they are. A lot of other languages have monads either baked in or an element of the design of libraries. Expand your mind out of the Haskell box :)


The things you listed may form a monad, but your list/iterator transformation doesn't use monadic composition.

> list.select { |x| x.foo > 10 }.map { |x| x.bar }

Where's `bind` or `join` in that example?


> It's Ruby

Thanks for clarifying; I've read a bunch of Ruby but never written it before ;)

From a quick Google I see that "select" and "map" do work as I thought:

https://ruby-doc.org/core-2.2.0/Array.html#method-i-select

https://ruby-doc.org/core-2.2.0/Array.html#method-i-map

So we have a value called "list", we're calling its "select" method/function and then calling the "map" method/function of that result. That's just function composition; no monads in sight!

To clarify, we can rewrite your example in the following way:

    list.select { |x|  x.foo > 10 }.map { |x| x.bar }

    # Define the anonymous functions/blocks elsewhere, for clarity
    list.select(checkFoo).map(getBar)

    # Turn methods into standalone functions
    map(select(list, checkFoo), getBar)

    # Swap argument positions
    map(getBar, select(checkFoo, list))

    # Curry "map" and "select"
    map(getBar)(select(checkFoo)(list))

    # Pull out definitions, for clarity
    mapper   = map(getBar)
    selector = select(checkFoo)
    mapper(selector(list))
This is function composition, which we could write:

    go = compose(mapper, selector)
    go(list)
The above argument is based solely on the structure of the code: it's function composition, regardless of whether we're using "map" and "select", or "plus" and "multiply", or any other functions.

To understand why "map" and "select" don't need monads, see below.

> the list could be an eager iterator, an actual list, a lazy iterator, a Maybe (though it would be clumsy in Ruby), etc.

Yes, that's because all of those things are functors (so we can "map" them) and collections (so we can "select" AKA filter them).

The interface for monad requires a "wrap" method (AKA "return"), which takes a single value and 'wraps it up' (e.g. for lists we return a single-element list). It also requires either a "bind" method ("concatMap" for lists) or, my preference, a "join" method ("concat" for lists).

I can show that your example doesn't involve any monads by defining another type which is not a monad, yet will still work with your example.

I'll call this type a "TaggedList", and it's a pair containing a single value of one type and a list of values of another type. We can implement "map" and "select" by applying them to the list; the single value just gets passed along unchanged. This obeys the functor laws (I encourage you to check this!), and whilst I don't know of any "select laws" I think we can say it behaves in a reasonable way.

In Haskell we'd write something like this (although Haskell uses different names, like "fmap" and "filter"):

    data TaggedList t1 t2 = T t1 [t2]

    instance Functor (TaggedList t1) where
      map f (T x ys) = T x (map f ys)

    instance Collection (TaggedList t1) where
      select f (T x ys) = T x (select f ys)
In Ruby we'd write something like:

    class TaggedList
      def initialize(x, ys)
        @x  = x
        @ys = ys
      end

      def map(f)
        TaggedList.new(@x, @ys.map(f))
      end

      def select(f)
        TaggedList.new(@x, @ys.select(f))
      end
    end
This type will work for your example, e.g. (in pseudo-Ruby, since I'm not so familiar with it):

    myTaggedList = TaggedList.new("hello", [{foo: 1, bar: true}, {foo: 20, bar: false}])
    result = myTaggedList.select { |x|  x.foo > 10 }.map { |x| x.bar }

    # This check will return true
    result == TaggedList.new("hello", [false])
Yet "TaggedList" cannot be a monad! The reason is simple: there's no way for the "wrap" function (AKA "return") to know which value to pick for "@x"!

We could write a function which took two arguments, used one for "@x" and wrapped the other in a list for "@ys", but that's not what the monad interface requires.

Since Ruby's dynamically typed (AKA "unityped") we could write a function which picked a default value for "@x", like "nil"; yet that would break the monad laws. Specifically:

    bind(m, wrap) == m
If "wrap" used a default value like "nil", then "bind(m, wrap)" would replace the "@x" value in "m" with "nil", and this would break the equation in almost all cases (i.e. except when "m" already contained "nil").


tiny clarification: Function composition is just function composition. Pipes are a different syntax for function application.

see https://news.ycombinator.com/item?id=18971451


More specifically, a pipe is a monad that abstracts your machine's state. It's basically equivalent to Haskell's IO.




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

Search: