Пару месяцев назад мне стало интересно, возможно ли сделать миксин, который ведет себя точно как Enumerable
, но основан на свёртывании вместо перечисления . Точнее, я уже знал , что это можно было сделать, поскольку fold - это общая итерационная операция, выраженная по степени выраженности foreach , но Я хотел знать, как это будет выглядеть и как тяжело это будет.
Мне стало скучно после того, как я реализовал большинство методов вплоть до буквы g , так что это неполно. Кроме того, версия, которую я показываю здесь, упрощена, поскольку заставить ее вести себя точно подобно Enumerable
- это PITA в Ruby. (Например, там есть перегруженные методы, но Ruby не поддерживает перегрузку. Это не проблема для большинства реализаций Ruby, потому что они реализуют Enumerable
в Java или C # или других языках, которые делают поддерживает перегрузку, но это довольно болезненно, когда вы делаете это в чистом Ruby, поэтому я решил оставить это без внимания.)
Enumerable
полон процедур высшего порядка, и, конечно, fold (или reduce
/ inject
, как его называют в Ruby) - это само по себе выше порядок, так что этот код полон их.
module Enumerable
def all?
reduce(true) {|res, el| res && yield(el) }
end
def any?
reduce(false) {|res, el| res || yield(el) }
end
def collect
reduce([]) {|res, el| res + yield(el) }
end
alias_method :map, :collect
def count
reduce(0) {|res, el| res + 1 if yield el }
end
def detect
reduce(nil) {|res, el| if yield el then el end unless res }
end
alias_method :find, :detect
def drop(n=1)
reduce([]) {|res, el| res.tap {|res| res + el unless n -= 1 >= 0 }}
end
def each
reduce(nil) {|_, el| yield el }
end
def each_with_index
reduce(-1) {|i, el| (i+1).tap {|i| yield el, i }}
end
def find_all
reduce([]) {|res, el| res.tap {|res| res + el if yield el }}
end
alias_method :select, :find_all
def grep(pattern)
reduce([]) {|res, el| res.tap {|res| res + yield(el) if pattern === el }}
end
def group_by
reduce(Hash.new {|hsh, key| hsh[key] = [] }) {|res, el| res.tap {|res|
res[yield el] << el
}}
end
def include?(obj)
reduce(false) {|res, el| break true if res || el == obj }
end
def reject
reduce([]) {|res, el| res.tap {|res| res + el unless yield el }}
end
end
Вы заметите, что я использую побочные эффекты в некоторых местах. Я мог бы написать это без побочных эффектов, но это было бы намного сложнее. И проблема в том, что я не могу гарантировать, что метод reduce
, который предоставляется клиентом миксина или любым из методов из базовой библиотеки, не имеет побочные эффекты.
Другим хорошим примером является библиотека Prelude для Ruby, которая представляет собой реализацию некоторых частей Haskell Prelude (эквивалент Haskell для базовой библиотеки Ruby) в Ruby.