Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
154 views
in Technique[技术] by (71.8m points)

functional programming - Monad equivalent in Ruby

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

question from:https://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...