Monad等价于Ruby
在Ruby中,monad的等价构造是什么?
精确的技术定义 :在Ruby中,monad将是任何具有bind
和self.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
使用这个,你可以以一种懒惰的方式把过程链接在一起。 例如,在下面的例子中, x
是一个容器“contains” 40
,但是直到第二行才会执行计算,这是由于puts
语句在调用force
之前不输出任何东西来certificate的:
x = Id.new(lambda {20}).bind[lambda {|x| puts x; Id.unit[x * 2]}] x.force
一个有点类似,不太抽象的例子是从数据库中获取值的monad。 假设我们有一个类Query
带有一个run(c)
方法,该方法需要一个数据库连接c
,以及一个Query
对象的构造函数,它们需要一个SQLstring。 所以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
这可以让你连接数据库通过一个单一的连接,如下所示:
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
好的,那你为什么要这样做呢? 因为有非常有用的function,可以为所有monad写一次。 因此,一旦你简单地实现unit
并bind
,那么你通常会一遍又一遍地写的代码可以被重用于任何monad。 例如,我们可以定义一个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 [ma] => 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
对象(将查询数组转换成返回数组的查询)或函数(将函数数组转换为函数返回一个数组)或数组(内部数组转换为数组),或者parsing器,延续,状态机或其他任何可能在Monad
模块中混合的数据(事实certificate,对于几乎所有的数据结构)。
为了增加我的两分钱,我会说hzap误解了单子的概念。 它不仅是一个“types界面”或“提供某些特定function的结构”,而且还比这更糟糕。 这是一个抽象的结构提供操作(绑定(>> =)和单位(返回)),如Ken和Apocalisp所说,严格的规则。
如果你对monads感兴趣,想知道更多关于它们的信息,那么我强烈build议你阅读Wadler的函数式编程Monads (pdf)。
再见!
PS:我看到我不直接回答你的问题,但是Apocalisp已经做到了,我想(至less是希望)我的精确度是值得的
单子不是语言结构。 它们只是实现特定接口的types,而且由于Ruby是dynamictypes的,所以实现类似于数组collect
类,连接方法(如flatten
但只扁平一个级别)以及可以包装任何东西的构造方法是一个monad。
继上面的答案:
您可能有兴趣查看Rumonade,这是一款为Ruby实现Monad混搭function的ruby 。
Romande是作为混合实现的,所以它期望它的宿主类实现方法self.unit
和#bind
(和可选的self.empty
),并且会做其余的事情来为你工作。
您可以使用它来map
Option
,就像您在Scala中使用的一样,甚至可以从validation中获得一些很好的多重失败返回值 ,一个是Scalaz的Validation类。