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

Что эквивалент создал бы из монады быть в Ruby?

39
задан 25 April 2010 в 18:25
поделиться

3 ответа

Точное техническое определение : монада в 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 (преобразование массива запросов в запрос, возвращающий массив), или функции (превращение массива функций в функцию, которая возвращает массив), или массивы (превращение массива массивов наизнанку), или синтаксические анализаторы, продолжения, конечные автоматы или что-либо еще, что может быть смешано в монаде (что, как оказалось, верно почти для всех структур данных).

74
ответ дан 27 November 2019 в 02:24
поделиться

Добавляя свои два цента, я бы сказал, что hzap неправильно понял концепцию монад. Это не только «интерфейс типов» или «структура, обеспечивающая некоторые специфические функции», это еще больше гадость . Это абстрактная структура, обеспечивающая операции (bind (>> =) и unit (return)), которые следуют, как сказали Кен и Апокалисп, строгим правилам.

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

Увидимся!

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

6
ответ дан 27 November 2019 в 02:24
поделиться

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

5
ответ дан 27 November 2019 в 02:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: