Существующие ответы уже обсуждали (и хорошо) функциональную природу Ruby (кстати, у меня есть запись здесь , которая может быть интересной). Теперь, отвечая на ваш вопрос: я бы сказал, что с любой точки зрения, не только с точки зрения FP, но и с точки зрения здравого смысла, операция фильтра всегда должна возвращать объект с оригиналом того же типа. Некоторые комментарии по вашему вопросу:
1) Вы удивляетесь, почему Hash#find_all
(= Hash#select
) возвращает массив. На самом деле, это не имеет смысла, тем более, когда Hash#reject
возвращает хеш.
>> {:a => 1, :b => 2}.select { |k, v| v > 1 } #=> [[:b, 2]]
>> {:a => 1, :b => 2}.reject { |k, v| v > 1 } #=> {:a=>1}
Но это давно было расценено как ошибка и, к счастью, решено в Ruby 1.9:
>> {:a => 1, :b => 2}.select { |k, v| v > 1 } #=> {:b=>2} # Ruby 1.9
>> {:a => 1, :b => 2}.reject { |k, v| v > 1 } #=> {:a=>1}
2) Ваш второй пример (String#each_char
) на самом деле не связан с этой проблемой. Этот метод возвращает перечисляемый («ленивый массив», если хотите) символов в строке, поэтому выбор / отклонение / ... 'над ним возвращает массив, это правильно. Что ж, чтобы быть православными, им следует вернуть также ленивый перечислитель, но у Руби все еще есть возможности для улучшения (посмотрите Facets ' Denumerator s, чтобы увидеть, как это правильно сделать).
3) @ Ed'ka представил обсуждаемую и интересную концепцию: fmap
. fmap
- это обобщенная версия map для функторов (которые представляют собой просто контейнеры, в которых вы можете выполнять итерацию. Примеры функторов: список, дерево, ассоциативный массив, множество, ...). В Ruby мы можем задаться вопросом, что Hash#map
должно возвращать .. массив? хэш? в Haskell, например, map
имеет смысл только для списков (хотя они совершенно другой природы, эквивалентными будут массивы в Ruby), поэтому может показаться приемлемым, что Hash#map
возвращает массив (альтернативой будет принудительное преобразование в сделать это более понятным: hash.to_a.map { |k, v| ... }
). Кстати, реализация Hash#fmap
в Ruby проста, я часто использую ее и включил в свой модуль расширений:
class Hash
def fmap(&block)
Hash[self.map(&block)]
end
end
{:a => 1, :b => 2}.fmap { |k, v| [k.to_s, v*2] } #=> {"a" => 2, "b" => 4}