Является ли возвращение Руби другого типа после фильтра необычным с точки зрения функционального программирования? - PullRequest
3 голосов
/ 21 апреля 2011

В Ruby есть несколько функций фильтра, которые производят тип, отличный от того, с чего вы начали.

Например, если вы делаете

{a: 2, b: 0}.find_all{|key, value| value.zero?}
# Use Hash[new_array] to turn it into a hash

вы получите массив ключей и значений, а не другой хеш.

А если ты сделаешь

str = "happydays"
all_indexes = [1, 2, 7, 8]
str.each_char.reject.with_index{|char, index| all_indexes.include?(index)}
# Use .join to turn it into a string

вы получите массив символов, а не строку.

Это нормально с точки зрения функционального программирования или это просто указывает на то, что Ruby не совсем реализует парадигму функционального программирования?

Ответы [ 4 ]

2 голосов
/ 21 апреля 2011

На каком языке «идеально реализуется парадигма функционального программирования»? Haskell, Erlang, Pure, OCaml, Clojure? Выберите свой выбор, они все имеют тенденцию делать вещи совершенно по-другому. Я действительно не пытаюсь вести здесь полемику (я управляю группой пользователей функционального программирования, где нам нравится обсуждать подобные вещи), но, как и в случае с ООП, существуют разные идеи относительно того, что влечет за собой функциональное программирование.

Теперь, хотя большинство людей не станет утверждать, что Haskell является лидером в чистоте, это ни в коем случае не единственный способ сделать FP. ИМХО Майкл Фогус и Крис Хаузер довольно хорошо подытожили это в «Радости Clojure»:

Проблемы функционального программирования и облегчает применение и композиция функций. В дальнейшем, для языка, который будет рассматриваться функциональный, его понятие функции должен быть первоклассным. Функции язык должен быть в состоянии быть сохраненным, прошло, и вернулся, как и любой другая часть данных в этом язык. Это за пределами этого ядра Концепция, что определения ветви к бесконечности, но, к счастью, это достаточно, чтобы начать.

Функция на самом деле не больше, чем какое-то отображение от домена к домену, и эти два, безусловно, не обязательно должны быть одинаковыми. Если вы посмотрите на функцию, подобную f(x) = sqrt(x), и предположите, что N (натуральные числа) являются доменом f, то совершенно очевидно, что кодомен не будет таким же (если вы не хотите, чтобы функция была неопределенной на больших отрезках) .

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

Как сказал Младен, есть много вещей, которые мешают Ruby быть чисто функциональным языком, но это верно для большинства языков, причем довольно многие из них сами являются функциональными языками (например, Clojure обычно предпочитает удобство использования и прагматизм, а не чистоту). Однако вполне возможно программировать в очень функциональном стиле на Ruby, если вы действительно этого хотите и обращаете внимание на некоторые детали. Вот несколько ссылок по теме:

1 голос
/ 17 июня 2011

Существующие ответы уже обсуждали (и хорошо) функциональную природу 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}
1 голос
/ 21 апреля 2011

Надеюсь, мы получим еще несколько ответов, так как мне это тоже интересно, но вот мое мнение:

Я не понимаю, почему тип , который возвращает некоторая функция corelib, подразумевает, что язык будет менее функциональным. Возьмите любой чисто функциональный язык, и вы сможете реализовать функцию, которая принимает что-то типа A и возвращает что-то типа B, и это то, что у вас там есть. Здесь мы можем просто обсудить причины, лежащие в основе решений для вышеперечисленных методов вернуть то, что они возвращают.

Есть и другие вещи, которые мешают Ruby быть чисто функциональным языком (изменчивость, для начала).

1 голос
/ 21 апреля 2011

По определению, парадигма функционального программирования избегает изменений в состоянии.И, исходя из этого определения и вашего приведенного примера, я думаю, что Ruby еще не полностью реализовал эту парадигму.Очевидно, это не страшно, поскольку у вас есть множество функций, таких как map, folds (inject), filters и т. Д., А также есть поддержка лямбда-функций / отложенного вычисления и т. Д.функциональный язык из-за его естественной поддержки для императивного / объектно-ориентированного программирования.Из-за этого разработчики Ruby могут сделать очень многое, чтобы сбалансировать этот мультипарадигмальный язык.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...