Точное техническое определение : монада в 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]}]
Некоторые практические примеры
Очень простым примером монады является ленивая монада Identity, которая имитирует ленивую семантику в 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
, но вычисление не выполняется до второй строки, о чем свидетельствует тот факт, что помещает
ничего не выводит до тех пор, пока не будет вызвана 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, который наделяет все такие классы некоторыми полезными методами:
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
Последовательность
Метод принимает класс, который смешивается с монадой, и возвращает функцию, которая принимает массив монадических значений и превращает его в монадическое значение, содержащее массив. Это могут быть значения Id
(преобразование массива идентификаторов в идентификатор, содержащий массив), или объекты DatabaseValue
(преобразование массива запросов в запрос, возвращающий массив), или функции (превращение массива функций в функцию, которая возвращает массив), или массивы (превращение массива массивов наизнанку), или синтаксические анализаторы, продолжения, конечные автоматы или что-либо еще, что может быть смешано в монаде
(что, как оказалось, верно почти для всех структур данных).
Добавляя свои два цента, я бы сказал, что hzap неправильно понял концепцию монад. Это не только «интерфейс типов» или «структура, обеспечивающая некоторые специфические функции», это еще больше гадость . Это абстрактная структура, обеспечивающая операции (bind (>> =) и unit (return)), которые следуют, как сказали Кен и Апокалисп, строгим правилам.
Если вас интересуют монады и вы хотите узнать о них больше, чем то, о чем говорится в этих ответах, я настоятельно рекомендую вам прочитать: Монады для функционального программирования (pdf), автор Wadler.
Увидимся!
PS: Я вижу, что не отвечаю прямо на ваш вопрос, но Apocalisp уже ответил, и я думаю (по крайней мере, надеюсь), что мои уточнения того стоили
Монады не являются языковыми конструкциями. Это просто типы, реализующие определенный интерфейс, и, поскольку Ruby динамически типизирован, любой класс, реализующий что-то вроде collect
в массивах, метод соединения (например, сглаживает
, но только сглаживает один level), а конструктор, который может обернуть что угодно, - это монада.