Зачем Enumerable#detect нужен Proc/лямбда?

Enumerable#detect возвращает первое значение массива, где блок оценивается как true. У него есть необязательный аргумент, который должен отвечать на call и вызывается в этом случае, возвращая его значение. Так,

(1..10).detect(lambda{ "none" }){|i| i == 11} #=> "none"

Зачем нам лямбда? Почему бы нам просто не передать само значение по умолчанию, поскольку (в моих тестах) лямбда все равно не может иметь никаких параметров? Как это:

(1..10).detect("none"){|i| i == 11} #=> "none"

person Max    schedule 02.01.2014    source источник
comment
Вы должны поместить туда объект, который должен реагировать на метод #call. Это может быть объект Proc или Method.   -  person Arup Rakshit    schedule 02.01.2014
comment
Рассмотрим (1..10).detect { |i| i == 11} || "none"   -  person Wayne Conrad    schedule 02.01.2014
comment
Хороший обходной путь, Уэйн!   -  person Max    schedule 04.01.2014


Ответы (5)


Как и во всем в Ruby, применяется «принцип наименьшего удивления». Что, конечно, не означает «наименьший сюрприз для вас». Матц совершенно откровенно говорит что это на самом деле означает:

У каждого индивидуальный бэкграунд. Кто-то может прийти с Python, кто-то может прийти с Perl, и они могут быть удивлены различными аспектами языка. Потом ко мне подходят и говорят: «Меня удивила эта особенность языка, значит, Ruby нарушает принцип наименьшего удивления». Ждать. Ждать. Принцип наименьшего удивления касается не только вас. Принцип наименьшего удивления означает принцип наименьшего моего удивления. И это означает принцип наименьшего удивления после того, как вы очень хорошо изучите Ruby. Например, я был программистом на C++ до того, как начал разрабатывать Ruby. Два или три года я программировал исключительно на C++. И после двух лет программирования на C++ это все еще меня удивляет.

Таким образом, рациональное здесь действительно чье-то предположение.

Одна из возможностей заключается в том, что он допускает или согласуется с вариантами использования, когда вы хотите условно запустить что-то дорогое:

arr.detect(lambda { do_something_expensive }) { |i| is_i_ok? i }

Или, как намекнул @majioa, возможно, передать метод:

arr.detect(method(:some_method)) { |i| is_i_ok? i }
person Denis de Bernardy    schedule 02.01.2014
comment
Я прочитал абзац, мне понравился абзац.. +1 за то, что взял его из Интернета. - person Arup Rakshit; 02.01.2014
comment
Передача метода имеет для меня наибольший смысл, поскольку я никогда не видел анонимных функций, используемых для передачи исключений. Тем не менее, это может быть моя интерпретация наименьшего удивления. - person Max; 04.01.2014

Принятие вызываемого объекта позволяет использовать «ленивые» и общие решения, например, в тех случаях, когда вы хотите сделать что-то дорогое, вызвать исключение и т. д.

Я не вижу причины, по которой detect не может принимать не вызываемые аргументы, особенно сейчас в Ruby 2.1, где легко создать дешевые замороженные литералы. Я открыл запрос функции по этому поводу.

person Marc-André Lafortune    schedule 02.01.2014
comment
Спасибо за отправку запроса. - person Max; 04.01.2014

Вероятно, поэтому вы можете получить соответствующий результат из ввода. Затем вы можете сделать что-то вроде

arr = (1..10).to_a
arr.detect(lambda{ arr.length }){|i| i == 11} #=> 10

Как вы сказали, вернуть постоянное значение с помощью лямбда в любом случае очень просто.

person Alex Siri    schedule 02.01.2014

Действительно это вопрос интереса. Я могу понять, почему авторы добавили функцию с вызовом метода, вы можете просто передать переменную method, содержащую объект Method или аналогичный, в качестве аргумента. Я думаю, что это просто волюнтаристское решение для метода :detect, потому что можно было бы легко добавить переключатель типа переданного аргумента, чтобы выбрать, является ли это Method или нет.

Я перепроверил примеры и получил:

(1..10).detect(proc {'wqw'})  { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
# => "wqw"
(1..10).detect('wqw')  { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
# NoMethodError: undefined method `call' for "wqw":String

Это удивительно. знак равно

person Малъ Скрылевъ    schedule 02.01.2014

Наилучший вариант использования лямбда-вызова — создать пользовательское исключение.

arr = (1..10).to_a
arr.detect(lambda{ raise "not found" }){|i| i == 11} #=> RuntimeError: not found

Таким образом, поскольку K тривиален (просто окружите его ->{ }), нет особого смысла проверять резервное поведение.

Аналогичный случай передачи символа &-ed вместо блока на самом деле совсем не похож, так как в этом случае он указывает что-то, что будет вызываться для элементов перечислимого.

person rewritten    schedule 02.01.2014