精确的技术定义:在 Ruby 中,一个 monad 可以是任何带有以下内容的类:bind
and 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
是一个“包含”的容器40
,但计算直到第二行才执行,事实证明puts
语句不会输出任何内容,直到force
叫做:
x = Id.new(lambda {20}).bind[lambda {|x| puts x; Id.unit[x * 2]}]
x.force
一个有点类似、不太抽象的例子是从数据库中获取值的 monad。假设我们有一堂课Query
with a 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
这将允许您通过单个连接链接数据库调用,如下所示:
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
and 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
这反过来又让我们可以做更多有用的事情,比如定义这个函数:
# 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
方法接受一个混合在 Monad 中的类,并返回一个函数,该函数接受一个 Monadic 值数组并将其转换为包含数组的 Monadic 值。他们可能是Id
值(将身份数组转换为包含数组的身份),或者DatabaseValue
对象(将查询数组转换为返回数组的查询),或函数(将函数数组转换为返回数组的函数),或数组(将数组数组由内而外转换),或解析器、延续、状态机或任何其他可能混合在Monad
模块(事实证明,这对于几乎所有数据结构都是如此)。