Monad相当于Ruby

39 ruby monads functional-programming

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

Apo*_*isp 74

精确的技术定义: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]}]
Run Code Online (Sandbox Code Playgroud)

一些实际的例子

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
Run Code Online (Sandbox Code Playgroud)

使用它,您可以以懒惰的方式将过程链接在一起.例如,在下面,x是一个容器"包含" 40,但直到第二行才执行计算,证明该puts语句在force调用之前不输出任何内容:

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

一个类似的,不那么抽象的例子是从数据库中获取值的monad.让我们假设我们有一个类,它Query带有一个run(c)接受数据库连接的方法c,以及一个Query带有SQL字符串的对象构造函数.因此DatabaseValue代表来自数据库的值.DatabaseValue是一个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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

好的,那你为什么要这样做呢?因为有非常有用的函数可以为所有monad写一次.因此,一旦你只是实现unit和编写,你通常可以反复编写的代码可以重用于任何monad bind.例如,我们可以定义一个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
Run Code Online (Sandbox Code Playgroud)

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

# 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
Run Code Online (Sandbox Code Playgroud)

sequence方法采用一个在Monad中混合的类,并返回一个函数,该函数接受一个monadic值数组并将其转换为包含数组的monadic值.它们可以是Id值(将一组Identities转换为包含数组的Identity)或DatabaseValue对象(将查询数组转换为返回数组的查询)或函数(将函数数组转换为返回数组的函数) ),或者数组(从内向外转换数组),或者解析器,连续符,状态机,或者其他任何可能混合在Monad模块中的东西(事实证明,几乎所有的数据结构都是如此).

  • 好吧,如果"新"满足左右单位法则,那将是等价的.不幸的是,方法,过程和块在Ruby中并不是完全相同的东西,更重要的是,*new是副作用*.我认为我在这里展示的是Ruby中monad的漂亮"OO"编码.除了Proc之外,还有另一种表达相同数据结构而不使用任何类的方法,这将是"非常实用"的实现.我认为"面向对象"无论如何都没有任何确切含义,所以我采取的态度是不考虑它. (3认同)
  • 请在这里查看实用的方法https://github.com/pzol/monadic (3认同)

rks*_*rks 6

为了增加我的两分钱,我会说hzap误解了monad的概念.它不仅仅是一个"类型接口"或"提供某些特定功能的结构",它还不仅仅是它.它是一个提供操作(bind(>> =)和unit(return))的抽象结构,正如Ken和Apocalisp所说,严格的规则.

如果你对monads感兴趣并希望了解更多关于它们的信息而不是这些答案中提到的一些内容,我强烈建议你阅读:Wadler编写的函数式编程Monad(pdf).

拜拜!

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


hza*_*zap 5

Monads不是语言结构.它们只是实现特定接口的类型,并且由于Ruby是动态类型的,任何实现像collect数组一样的类,一个连接方法(比如flatten只能展平一个级别),以及一个可以包装任何东西的构造函数,都是一个monad .

  • 它必须正确地实施它们,以便它们遵守monad法则. (9认同)