Подтвердить что ты не робот

Монад-эквивалент в Ruby

Какова будет эквивалентная конструкция монады в Ruby?

4b9b3361

Ответ 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 (который, как оказалось, верно для почти всех структур данных).

Ответ 2

Чтобы добавить мои два цента, я бы сказал, что hzap неправильно понял концепцию монадов. Это не только "интерфейс типа", либо "структура, обеспечивающая некоторые конкретные функции", она больше гасит. Это абстрактная структура, обеспечивающая операции (bind ( → =) и unit (return)), которые следуют, как говорят Ken и Apocalisp, строгие правила.

Если вас интересуют монады и вы хотите узнать о них больше, чем несколько вещей, упомянутых в этих ответах, я настоятельно рекомендую вам прочитать: Monads для функционального программирования (pdf), Wadler.

См. ya!

PS: Я вижу, что я не отвечаю прямо на ваш вопрос, но Apocalisp уже сделал, и я думаю (по крайней мере надеюсь), что мои исправления стоили того.

Ответ 3

Монады не являются языковыми конструкциями. Это просто типы, которые реализуют определенный интерфейс, и поскольку Ruby динамически типизирован, любой класс, реализующий что-то вроде collect в массивах, метод объединения (например, flatten, но только выравнивает один уровень) и конструктор, который может обернуть что-нибудь, это монада.

Ответ 4

Следуя приведенным выше ответам:

Вам может быть интересно проверить Rumonade, рубиновый камень, который реализует микширование Monad для Ruby.

Romande реализуется как mix-in, поэтому он ожидает, что его хост-класс реализует методы self.unit и #bind (и, необязательно, self.empty), и сделает все остальное, чтобы заставить вас работать.

Вы можете использовать его для map более Option, поскольку вы привыкли в Scala, и вы даже можете получить несколько приятных multiple- значения возврата отказа от валидации, класс проверки la Scalaz.