Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Enumerable#detect need a Proc/lambda?

Enumerable#detect returns the first value of an array where the block evaluates to true. It has an optional argument that needs to respond to call and is invoked in this case, returning its value. So,

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

Why do we need the lambda? Why don't we just pass the default value itself, since (in my tests) the lambda can't have any parameters anyway? Like this:

(1..10).detect("none"){|i| i == 11} #=> "none"
like image 468
Max Avatar asked Jan 02 '14 12:01

Max


4 Answers

As with all things in Ruby, the "principle of least surprise" applies. Which is not to say "least surprise for you" of course. Matz is quite candid about what it actually means:

Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, 'I was surprised by this feature of the language, so Ruby violates the principle of least surprise.' Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprises me.

So, the rational here is really anyone's guess.

One possibility is that it allows for or is consistent with use-cases where you want to conditionally run something expensive:

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

Or as hinted by @majioa, perhaps to pass a method:

arr.detect(method(:some_method)) { |i| is_i_ok? i }
like image 123
Denis de Bernardy Avatar answered Oct 20 '22 05:10

Denis de Bernardy


Accepting a callable object allows allows "lazy" and generic solutions, for example in cases where you'd want to do something expensive, raise an exception, etc...

I can't see a reason why detect couldn't accept non callable arguments, though, especially now in Ruby 2.1 where it's easy to create cheap frozen litterals. I've opened a feature request to that effect.

like image 40
Marc-André Lafortune Avatar answered Oct 20 '22 03:10

Marc-André Lafortune


It is probably so you can generate an appropiate result from the input. You can then do something like

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

As you said, returning a constant value is very easy with a lambda anyway.

like image 1
Alex Siri Avatar answered Oct 20 '22 04:10

Alex Siri


Really it is interestion question. I can understand why authors have added the feature with method call, you can just pass method variable, containing a Method object or similar, as an argument. I think it is simply voluntaristic solution for the :detect method, because it could be easy to add switch on type of passed argument to select weither it is the Method or not.

I've reverified the examples, and got:

(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

That is amazing. =)

like image 1
Малъ Скрылевъ Avatar answered Oct 20 '22 04:10

Малъ Скрылевъ