39

在 Ruby 中,monad 的等效构造是什么?

4

4 回答 4

75

精确的技术定义:在 Ruby 中,monad 可以是任何具有定义的类bindself.unit方法,使得对于所有实例 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]}]

一些实际例子

一个非常简单的 monad 示例是惰性 Identity monad,它模拟 Ruby(一种严格的语言)中的惰性语义:

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

使用它,您可以以惰性方式将 proc 链接在一起。例如,在下面,x是一个容器 "containing" 40,但是直到第二行才执行计算,这一事实证明了该语句在被调用puts之前不输出任何内容:force

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

一个有点类似但不那么抽象的例子是从数据库中获取值的 monad。假设我们有一个类,该类Query具有一个run(c)采用数据库连接的方法c,以及一个Query采用 SQL 字符串的对象构造函数。SoDatabaseValue表示来自数据库的值。DatabaseValue 是一个单子:

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

这将允许您通过单个连接链接数据库调用,如下所示:

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

好吧,那你到底为什么要这么做?因为可以为所有 monad编写一次非常有用的函数。因此,一旦您简单地实现了unitbind. 例如,我们可以定义一个 Monad mixin,为所有此类类赋予一些有用的方法:

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

这反过来又让我们做更多有用的事情,比如定义这个函数:

# 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

sequence方法接受一个混入 Monad 的类,并返回一个函数,该函数接受一个 monadic 值数组并将其转换为包含数组的 monadic 值。它们可以是Id值(将标识数组转换为包含数组的标识)、DatabaseValue对象(将查询数组转换为返回数组的查询)或函数(将函数数组转换为返回数组的函数) ),或数组(将数组数组从里到外),或解析器、延续、状态机或任何其他可能混入Monad模块的东西(事实证明,几乎所有数据结构都是如此)。

于 2010-04-25T18:53:03.730 回答
6

加上我的两分钱,我想说 hzap 误解了单子的概念。它不仅仅是一个«类型接口»或一个«提供某些特定功能的结构»,它还不止于此。它是一个提供操作(绑定(>>=)和单元(返回))的抽象结构,正如 Ken 和 Apocalisp 所说,遵循严格的规则。

如果您对 monad 感兴趣并且想了解更多关于它们的信息,而不是这些答案中所说的几件事,我强烈建议您阅读:Wadler 的 Monads for functional programming (pdf)。

拜拜!

PS:我看我没有直接回答你的问题,但是 Apocalisp 已经回答了,我认为(至少希望)我的精确度是值得的

于 2010-04-25T19:26:50.667 回答
5

单子不是语言结构。它们只是实现特定接口的类型,并且由于 Ruby 是动态类型的,任何实现类似collect数组、连接方法(类似flatten但仅展平一层)和可以包装任何东西的构造函数的类都是 monad .

于 2010-04-25T18:35:59.777 回答
1

根据上述答案:

您可能有兴趣查看Rumonade,这是一个为 Ruby 实现 Monad 混入的 ruby​​ gem

Romande 是作为 mix-in 实现的,因此它希望它的宿主类实现方法self.unit#bind(以及可选的self.empty),并将完成其余的工作以使事情为您工作。

您可以使用它来mapover Option,就像您在 Scala 中习惯的那样,您甚至可以从 validations (一个 Scalaz 的 Validation 类)中获得一些不错的多次失败返回值。

于 2012-05-02T21:58:25.750 回答