Question

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"
Was it helpful?

Solution

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 }

OTHER TIPS

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.

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.

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. =)

The best use case for having a lambda is to raise a custom exception

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

So, as the K is trivial (just surround with ->{ }) there is not much sense in checking for fallback behavior.

The similar case of passing a &-ed symbol instead of a block is in fact not similar at all, as in that case it indicates something that will be called on the items of the enumerable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top