代码之家  ›  专栏  ›  技术社区  ›  fig

红宝石中的单子等价物

  •  39
  • fig  · 技术社区  · 16 年前

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

    4 回复  |  直到 16 年前
        1
  •  74
  •   Apocalisp    14 年前

    精确的技术定义 :在Ruby中,monad是任何具有 bind 和 self.unit

    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的一个非常简单的例子是lazy Identity monad,它在Ruby(一种严格的语言)中模拟了lazy语义:

    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
    

    使用这个,您可以以一种懒惰的方式将进程链接在一起。例如,在下面的例子中, x 容器是否“包含” 40 ,但直到第二行才执行计算,事实证明 puts 语句在 force 称为:

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

    一个类似的、不那么抽象的例子是从数据库中获取值的monad。假设我们有一节课 Query 用一个 run(c) 获取数据库连接的方法 c ,以及 查询 DatabaseValue 表示来自数据库的值。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重用 unit 和 绑定

    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 Id 值(将标识数组转换为包含数组的标识),或 数据库值 对象(将查询数组转换为返回数组的查询),或函数(将函数数组转换为返回数组的函数),或数组(将数组由内向外转换),或解析器、continuations、状态机,或任何其他可能在 Monad 模块(事实证明,这几乎适用于所有数据结构)。

        2
  •  6
  •   rks    16 年前

    加上我的两分钱,我想说hzap误解了单子的概念。 它不仅仅是一个类型接口或一个提供特定功能的结构,它还不止这些。 它是一个提供操作的抽象结构(bind>>=)和单位(返回)),正如肯和阿波卡利普所说的,遵循严格的规则。

    如果你对monads感兴趣,并且想知道更多关于monads的信息,而不是这些答案中所说的几件事,我强烈建议你阅读: Monads for functional programming

    再见!

        3
  •  5
  •   hzap    16 年前

    单子不是语言结构。它们只是实现特定接口的类型,由于Ruby是动态类型化的,所以任何实现 collect 在数组中,连接方法(如 flatten

        4
  •  1
  •   ms-tg    14 年前

    以下是上述答案:

    你可能有兴趣退房 Rumonade, a ruby gem which implements a Monad mix-in for Ruby .

    Romande实现为mix-in,因此它希望它的宿主类实现这些方法 self.unit 和 #bind (可选地, self.empty ),并会做剩下的事情,使你的工作。

    你可以用它来 map 结束 Option multiple-failure return values from validations ,一个la Scalaz的验证类。

    推荐文章