0

Does any? break from the loop when a match is found?

The following is the any? source code, but I don't understand it.

static VALUE
enum_any(VALUE obj)
{
    VALUE result = Qfalse;

    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(any), (VALUE)&result);
    return result;
}
sawa
  • 165,429
  • 45
  • 277
  • 381
Alejandro Marti
  • 113
  • 1
  • 6
  • 4
    The Ruby answers prove that it does short-circuit; but if you still want to find where that is in C, look at where `ENUMFUNC(any)` is defined (look for `DEFINE_ENUMFUNCS(any)`), and you should see `rb_iter_break()` on a match. (BTW, your source is a little old; [here](https://github.com/ruby/ruby/blob/8099b9279b7401fa393385611a4c159900582105/enum.c)'s the MRI 2.5 file.) – Amadan Jul 25 '18 at 10:53
  • Thank you for the depth on the answer. Next time I'll be able to go further on my search – Alejandro Marti Jul 25 '18 at 11:07

4 Answers4

4

The term is "short-circuiting" and yes, any? does that. After it finds a match, it doesn't look any further.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
4

Yes, it does break the loop. One does not need to dig into code to check that:

[1,2,3].any? { |e| puts "Checking #{e}"; e == 2 }
# Checking 1
# Checking 2
#⇒ true
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

Does any? break from the loop when a match is found?

The documentation is unclear about that:

The method returns true if the block ever returns a value other than false or nil.

Note: it does not say "when the block ever returns a value other than false or nil" or "as soon as the block ever returns a value other than false or nil".

This can be interpreted either way, or it can be interpreted as making no guarantees at all. If you go by this documentation, then you can neither guarantee that it will short-ciruit, nor can you guarantee that it won't short-circuit.

Generally speaking, this is typical for API specifications: make the minimum amount of guarantees, giving the API implementor maximum freedom in how to implement the API.

There is somewhere else we can look: the ISO Ruby Programming Language Specification (bold emphasis mine):

15.3.2.2.2 Enumerable#any?

any?(&block)

Visibility: public

Behavior:

a) Invoke the method each on the receiver

b) For each element X which each yields

  1. If block is given, call block with X as the argument. If this call results in a trueish object, return true

As you can see, again it only says "if", but not "when" or "as soon as". This sentence can be interpreted in two ways: "Return true as the result of the method" (no indication of how often the block gets called, only that the method will return true at the end) or "return true when you encounter an invocation of the block that evaluates to a trueish value".

Try #3: The Ruby Spec:

it "stops iterating once tähe return value is determined" do

So, yes, we can indeed rely on the fact that the block is only evaluated until the first truthy value is encountered.

The following is the any? source code, but I don't understand it.

Note: by looking at the source code, you can not determine how something behaves in Ruby. You can only determine how something behaves in that specific version of that specific implementation of Ruby. Different implementations may behave differently (for example, in YARV, Ruby threads cannot run at the same time, in JRuby, they can). Even different versions of the same implementation can behave differently.

It is usually not a good idea to make assumptions about the behavior of a programming language by just looking at a single version of a single implementation.

However, if you really want to look at some implementation, and are fully aware about the limitations of this approach, then I would suggest to look at Rubinius, Topaz, Opal, IronRuby, or JRuby. They are (in my opinion) better organized and easier to read than YARV.

For example, this is the code for Enumerable#any? in Rubinius:

def any?
  if block_given?
    each { |*element| return true if yield(*element) }
  else
    each { return true if Rubinius.single_block_arg }
  end
  false
end

This looks rather clear and readable, doesn't it?

This is the definition in Topaz:

def any?(&block)
  if block
    self.each { |*e| return true if yield(*e) }
  else
    self.each_entry { |e| return true if e }
  end
  false
end

This also looks fairly readable.

The soure in Opal is a little bit more complex, but only marginally so:

def any?(pattern = undefined, &block)
  if `pattern !== undefined`
    each do |*value|
      comparable = `comparableForPattern(value)`

      return true if pattern.public_send(:===, *comparable)
    end
  elsif block_given?
    each do |*value|
      if yield(*value)
        return true
      end
    end
  else
    each do |*value|
      if Opal.destructure(value)
        return true
      end
    end
  end

  false
end

[Note the interesting use of overriding the ` method for injecting literal ECMAScript into the compiled code.]

Most of the added complexity compared to the Rubinius and Topaz versions stems from the fact that Opal already supports the third overload of any? taking a pattern which was introduced in Ruby 2.5, whereas Rubinius and Topaz only support the two overloads with a block and without any arguments at all.

IronRuby's implementation implements the short-circuiting like this:

if (predicate.Yield(item, out blockResult)) {
    result = blockResult;
    return selfBlock.PropagateFlow(predicate, blockResult);
}

JRuby's implementation is a little bit more involved still, but you can see that as soon as it encounters a truthy block value, it breaks out of the loop by throwing a SPECIAL_JUMP exception and catching it to return true.

Community
  • 1
  • 1
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Awesome answer. However, most Rubyists will agree that whatever YARV does it's de facto standard, and all the other implementations either follow YARV or are explicit about it when they don't. (And I don't see ISO spec as particularly relevant, as it describes a dead language, as of June 2013.) – Amadan Jul 30 '18 at 07:37
1

Yes and it's easy to prove:

irb(main):009:0> %w{ant bear cat}.any? {|word| puts "hello"; word.length >= 4}
hello
hello
=> true

It has printed only twice. If it did not break it would print 3 times.

Joao Cunha
  • 772
  • 4
  • 15