Ответ 1
Точное техническое определение: монада в Ruby будет любым классом с методами 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]}]
Некоторые практические примеры
Очень простой пример монады - ленивая монада Идентичности, которая эмулирует ленивую семантику в 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
Несколько похожий, менее абстрактный пример - это монада для получения значений из базы данных. Предположим, что у нас есть класс Query
с методом run(c)
, который принимает соединение с базой данных c
и конструктор объектов Query
, который принимает, скажем, строку SQL. Таким образом, 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
ОК, так почему бы вам это сделать? Потому что есть чрезвычайно полезные функции, которые можно записать один раз для всех монадов. Поэтому код, который вы обычно пишете снова и снова, может быть повторно использован для любой монады, как только вы просто реализуете unit
и 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
Метод sequence
принимает класс, который смешивается в Monad, и возвращает функцию, которая принимает массив монадических значений и превращает его в монадическое значение, содержащее массив. Они могут быть Id
значениями (превращение массива идентификаторов в идентификатор, содержащий массив) или DatabaseValue
объектов (превращение массива запросов в запрос, возвращающий массив) или функции (превращение массива функций в функция, возвращающая массив) или массивы (превращение массива массивов изнутри) или парсеры, продолжения, конечные машины или что-то еще, что может смешиваться в модуле Monad
(который, как оказалось, верно для почти всех структур данных).