views:

694

answers:

4

What would an equivalent construct of a monad be in Ruby?

+4  A: 

There's no such thing as a monad type class in ruby (which wouldn't make sense anyway since ruby is dynamically typed), but there are of course equivalents to specific monads. For example some of the most commonly used monads in haskell are List, Maybe and IO.

Where one would use a list in haskell one usually uses arrays in ruby. Ruby's array class offers most of the higher order functions known from haskell like map, select (filter), inject (foldl) etc. (Array inherits them from Enumerable to be precise). Using map + flatten(1) (or flat_map in 1.9.2) you have the behavior of >>= and by simply creating a one-element array you have the behavior of return. If one really needs a linked list, there is a gem for that somewhere (ruby does not have linked lists in the standard library).

Where one would use Maybe in haskell one would usually just return nil or the desired object in ruby. Some people monkey-patch nil so that any method call on nil will result in nil instead of throwing an Exception. This allows them to perform a couple of operations and have the end-result be nil if any of the intermediary results were nil (same as the Maybe monad in haskell does). This is generally considered bad practice though, since this will change the behavior of nil globally.

Where one would use IO in haskell, one would usually just use IO functions like puts and gets in ruby (there is no need to abstract away side-effect in a decidedly impure language).

sepp2k
Why was this downvoted?
sepp2k
I wasn't the downvoter, but I'd like to point out that the point of classifying lists, maybes and IO into the single concept of "monad" is that you can define generic functions over all monads (e.g. Haskell's `sequence`). If you have different interfaces between a maybe monad and a list monad, this is no longer possible, and the fact that it is a monad becomes less useful.
hzap
+5  A: 

Monads are not language constructs. They're just types that implement a particular interface, and since Ruby is dynamically typed, any class that implement something like collect in arrays, a join method (like flatten but only flattens one level), and a constructor that can wrap anything, is a monad.

hzap
It has to implement them correctly, so that they obey the monad laws.
Ken Bloom
+33  A: 

The precise technical definition: A monad, in Ruby, would be any class with bind and self.unit methods defined such that for all instances m:

m.class.unit[a].bind[f] == f[a]
m.bind[m.class.unit] == m  
m.bind[f].bind[g] == m.bind[lambda {|x| f[x].bind[g]}]

Some practical examples

A very simple example of a monad is the lazy Identity monad, which emulates lazy semantics in Ruby (a strict language):

class Id
  def initialize(lam)
    @v = lam
  end

  def force
    @v[]
  end

  def self.unit
    lambda {|x| Id.new(lambda { x })}
  end

  def bind
    x = self
    lambda {|f| Id.new(lambda { f[x.force] })}
  end
end

Using this, you can chain procs together in a lazy manner. For example, in the following, x is a container "containing" 40, but the computation is not performed until the second line, evidenced by the fact that the puts statement doesn't output anything until force is called:

x = Id.new(lambda {20}).bind[lambda {|x| puts x; Id.unit[x * 2]}]
x.force

A somewhat similar, less abstract example would be a monad for getting values out of a database. Let's presume that we have a class Query with a run(c) method that takes a database connection c, and a constructor of Query objects that takes, say, an SQL string. So DatabaseValue represents a value that's coming from the database. DatabaseValue is a monad:

class DatabaseValue
  def initialize(lam)
    @cont = lam
  end

  def self.fromQuery(q)
    DatabaseValue.new(lambda {|c| q.run(c) })
  end

  def run(c)
    @cont[c]
  end

  def self.unit
    lambda {|x| DatabaseValue.new(lambda {|c| x })}
  end

  def bind
    x = self
    lambda {|f| DatabaseValue.new(lambda {|c| f[x.run(c)].run(c) })}
  end
end

This would let you chain database calls through a single connection, like so:

q = unit["John"].bind[lambda {|n|
  fromQuery(Query.new("select dep_id from emp where name = #{n}")).
    bind[lambda {|id|
      fromQuery(Query.new("select name from dep where id = #{id}"))}].
        bind[lambda { |name| unit[doSomethingWithDeptName(name)] }]

begin
  c = openDbConnection
  someResult = q.run(c)
rescue
  puts "Error #{$!}"
ensure
  c.close
end

OK, so why on earth would you do that? Because there are extremely useful functions that can be written once for all monads. So code that you would normally write over and over can be reused for any monad once you simply implement unit and bind. For example, we can define a Monad mixin that endows all such classes with some useful methods:

module Monad
  I = lambda {|x| x }

  # Structure-preserving transform that applies the given function
  # across the monad environment.
  def map
    lambda {|f| bind[lambda {|x| self.class.unit[f[x]] }]}
  end

  # Joins a monad environment containing another into one environment.
  def flatten
    bind[I]
  end

  # Applies a function internally in the monad.
  def ap
    lambda {|x| liftM2[I,x] }
  end

  # Binds a binary function across two environments.
  def liftM2
    lambda {|f, m|
      bind[lambda {|x1|
        m.bind[lambda {|x2|
          self.class.unit[f[x1,x2]]
        }]
      }]
    }
  end
end

And this in turn lets us do even more useful things, like define this function:

# An internal array iterator [m a] => m [a]
def sequence(m)
  snoc = lambda {|xs, x| xs + [x]}
  lambda {|ms| ms.inject(m.unit[[]], &(lambda {|x, xs| x.liftM2[snoc, xs] }))}
end

The sequence method takes a class that mixes in Monad, and returns a function that takes an array of monadic values and turns it into a monadic value containing an array. They could be Id values (turning an array of Identities into an Identity containing an array), or DatabaseValue objects (turning an array of queries into a query that returns an array), or functions (turning an array of functions into a function that returns an array), or arrays (turning an array of arrays inside-out), or parsers, continuations, state machines, or anything else that could possibly mix in the Monad module (which, as it turns out, is true for almost all data structures).

Monads provide a complete model of computation, so they are extremely powerful and flexible.

Apocalisp
Awesome explanation! Just a question: this seems to be a very functional implementation of Monads. In a more object-oriented implementation, wouldn't `unit` just be `new`? (Actually, I see now that the types don't quite match up. So, the more interesting question would be: is there a way to make `unit` be `new`? And what would object-orientation look like when we did it the other way round: make `new` behave like `unit`?)
Jörg W Mittag
Well, it would be equivalent if `new` satisfied the left and right unit laws. Unfortunately, methods, procs, and blocks aren't really all the same thing in Ruby, and importantly, *new is a side-effect*.I think what I've shown here is a pretty "OO" encoding of monads in Ruby. There's another way of expressing the same data structures without using any classes at all except Proc, and that would be the "very functional" implementation.I don't think that "object-oriented" has any precise meaning anyway, so I take the position of just not thinking about it.
Apocalisp
+4  A: 

To add my two cents, I'd say that hzap has misunderstood the concept of monads. It's not only a « type interface » or a « structure providing some specific functions », it's muck more than that. It's an abstract structure providing operations (bind (>>=) and unit (return)) which follow, as Ken and Apocalisp said, strict rules.

If you're interested by monads and want to know more about them than the few things said in these answers, I strongly advise you to read : Monads for functional programming (pdf), by Wadler.

See ya!

PS: I see I don't directly answer your question, but Apocalisp already did, and I think (at least hope) that my precisions were worth it

rks