Загрузка...

Ruby & Rails: веб-разработка с удовольствием

Ruby on Rails — фреймворк для создания веб-приложений. Является открытым программным обеспечением (лицензия MIT). Здесь мы обсуждаем новости RoR, делимся учебными материалами и интересными находками С RoR даже сложные веб-приложения могут быть написаны за считанные дни. Это действительно разработка с удовольствием!
     

ФП в руби

21.08.09, 18:00
Автор mshakhan

Начитавшись как-то на ночь про всякие хаскеллы и лиспы, решил немного "офункционалить" руби. Первое, в чем я разобрался, было "pattern-matching" определение методов. Вот об этом и хочу рассказать.

Способ определения методов через сопоставление с образцом покажу на примере:

factorial :: Integer -> Integer
factorial 0 = 1
factorial n | n > 0 = n * factorial (n-1)

Грубо говоря (если я не прав, поправьте меня), мы определяем как бы несколько методов с определенным набором параметров (похоже на перегрузку методов).

Посидев несколько часов над этим делом, у меня получилась библиотека, с помощью которой можно писать следующие вещи:

module Math
  class < < self
    include FRuby::Definer
    define(:fact, Fixnum) { |n| n * fact(n - 1) }
    define(:fact, 0) { 1 }
    define(:fact, 1) { 1 }
  end
end
Math::fact(5) # => 120

Пару слов о реализации. При каждом вызове define определяется новый приватный метод (с именем, совпадающим с именем первого аргумента + случайная строка) и делается алиас на "старый метод". В этом методе производится проверка аргументов и вызов либо переданного блока (в случае успеха при проверке аргументов), либо вызов "старого" метода.

Все делалось just for fun, но, возможно (при определенной доработке), возможно даже применять данный паттерн в боевых условиях (особенно в случаях, где не особо важна производительность, так как при каждом вызове метода просиходит проверка всех вариантов аргументов).

Полностью код лежит здесь. Кстати, он оформлен в виде гема (и даже частично проспекован =), и, если интересно, то могу написать еще одну статью о том, как сделать свой гем и разместить его на гитхабе.

Предложения/замечания/дополнения/форки приветствуются =)

Комментарии

имхо лучше заменить рекурсию в define(:fact, Fixnum) на перемножение всех чиселок, ибо текущий вариант падает в stack level too deep.

define(:fact, Fixnum) { |n| (1..n).reduce { |mem, cur| mem *= cur } }

Еще надо бы описать случай для отрицательных чисел для полноты картины

Да, при достаточно большом аргументе упадет.
Но в примере сделано именно так, чтобы показать, что будут вызываться различные методы, в зависимости от аргумента. То есть рекурсия прервется на n=1.

Кстати, в планах еще добавить возможность именовать эти методы, то есть
define(:some_method, Symbol, :name => :some_method_symbol)
define(:some_method, Hash, :name => :some_method_hash)
Чтобы можно было вызывать напрямую, минуя проверки аргументов.

Для отрицательных чисел этот паттерн не покатит (нет, коечно покатит, если иф в методе написать). Для этого надо использовать “охраные выражения”, или как они там называются =)
Думаю, что хаскелловское
factorial n | n > 0 = n * factorial (n-1)
Можно перевести на руби примерно так:
define(:fact, :args => { :n => Fixnum }, :where => { :n => proc { |n| n > 0 } } )
Ну или как-то так.

мне кажется, не имеет смысла вносить в сигнатуру метода тип данных – на то он и руби с утипизацией чтобы все сам разруливать. Может быть, лучше в кишках делать pattern matching по respond_to?

Немного не понял, как по respond_to? можно однозначно отличить один ти от другого. У них могут быть методы с одинаковым названием, но с совершенно разным значением.
В моем понимании, тип данных в аргументе важен. Это позволяет разруливать вызовы по типу аргументов, без кучи if-ов внутри метода. Представьте для примера, что мы пишем ORM, и в классе сущности есть метод find, который может принимать либо просто id сущности, либо хеш параметров для поиска. Вот как это можно реализовать с fruby:
define(:find, Integer) do |id|

  1. find by enitity id
    end
    define(:find, Hash) do |attributes|
  2. find by attributes
    end
    Другое дело, что реализовано это пока достаточно криво =) Например, если ваш метод принимает класс как параметр, то он вас не поймет и будет матчить по типу. Нужно дифференцировать типы и значения.

тьфу, не respond_to? а instance_of? – эффект пятницы вечера. ri утверждает, что:

Object#instance_of? obj.instance_of?(class) => true or false Returns true if obj is an instance of the given class. See also Object#kind_of?.

Факториал без pattern matching выглядит куда приятнее; так что тема полезности, так сказать, не раскрыта. Хотелось бы примера поубедительней.
А вот, например, балансировка красно-черных деревьев с ним сокращается раз в 5. Потянет ее твоя ruby-библиотечка?

http://www.google.com/codesearch/p?hl=en&amp;sa=N&amp;cd=2&amp;ct=rc#7L1T6wOwwU0/OCLGF/GF2/GF/Data/RedBlackSet.hs&amp;q=red-black%20lang:haskell Вот тут, rbBalance.

Войдите, чтобы оставить комментарий