Theodore
Theodore

Reputation: 223

What does a ruby 'do' iteration loop evaluate to?

I have the following ruby method: a single do iteration without any break, next, or return. Here, cats is an array of cat objects; is_cat_red evaluates to true if cat has a color property of red.

def get_non_red_cats cats
  cats.each do |cat|
    !is_cat_red?(cat)
  end
end

What does the method return (what does the loop evaluate to)?

Upvotes: 1

Views: 80

Answers (2)

Jörg W Mittag
Jörg W Mittag

Reputation: 369458

There is no loop in your code. Ruby has two kinds of loops: while and for/in. (Actually, the latter is just syntactic sugar for each.)

In Ruby, an expression evaluates to the value of the last sub-expression evaluated inside the expression. A message send evaluates to the return value of the method that was executed as a result of the message send. The return value of a method is either explicitly the value of the return expression that ended the method execution or implicitly the value of the last expression evaluated inside the method body. (Note that the last expression evaluated inside the body is also what a module or class definition expression evaluates to. A method definition expression however evaluates to a Symbol denoting the name of the method.)

So, what does get_non_red_cats return? Well, there is no return in it, so it returns the value of the last expression evaluated inside the method body. The last expression evaluated inside the method body is a message send of the message each to the object referenced by the parameter binding cats. Ergo, the return value of get_non_red_cats is the return value of the method that gets executed as a result of sending the each message to cats.

And that is all we positively know.

We can make some assumptions, though. In general, each should return self. That's what all implementations of each in the entire core library and standard library do, and it is part of the standard "Iterable" Protocol in Ruby. It would be highly unusual and highly confusing if that were not the case. So, we can assume that whatever implementation of each ends up being executed, it will return self, i.e. the receiver of the message send, i.e. the object referenced by the parameter binding cats.

In other words: the method get_non_red_cats simply returns whatever was passed in as an argument. It is a pretty boring method. In fact, it is the identity method, which is pretty much the most boring method possible.

However, it could have a side-effect. You didn't ask about side-effects, only the return value, but let's look at it anyway.

Since each is supposed to simply return its receiver, it is in some sense also an identity method and thus extremely boring. However, each is generally supposed to evaluate the block it is passed, passing each element of the collection in turn as an argument. But, it ignores the value that the block evaluates to; the block is evaluated purely for its side-effect. Note that each with a block that has no side-effect makes no sense whatsoever. If the block has no side-effect, then the only thing interesting about the block is its value, but each ignores the block's value, and simply returns self.

foo.each do
  # something that has no side-effect
end

is fully equivalent to

foo

Another Ruby convention is that message sends that end in a question mark ? should be used for asking questions (duh!) I.e. a message send that ends in a question mark should return something that is suitable to used as a conditional. It also generally shouldn't have a side-effect. (This is called the Command-Query Separation Principle and is a fundamental design principle of Object-Oriented Software Construction.)

And lastly, the ! unary prefix operator, when applied to something that is intended to be used in a conditional (i.e. a boolean value or something equivalent) is generally not supposed to have side-effect. Ergo, since the message send in the block ends with a question mark, it is not supposed to have a side-effect, and the ! operator is also not supposed to have a side-effect, we can assume that the entire block has no side-effect.

This, in turn, means that each shouldn't have a side-effect, and thus get_non_red_cats doesn't have a side-effect. As a result, the only other thing get_non_red_cats can do, is return a value, and it very likely simply returns the value that was passed in.

Ergo, the entire method is equivalent to

def get_non_red_cats(cats)
  cats
end

All of this is assuming that the author followed standard Ruby conventions. If she didn't, then this method could do and return anything whatsoever, it could format your harddrive, launch a nuclear attack, return 42, or do absolutely nothing at all.

Upvotes: 1

tadman
tadman

Reputation: 211560

This is some unusual code and it depends entirely on what the cats method does. You can pass a block to any Ruby method and that method can get executed zero more more times at any point between immediately and the end of the program's execution.

The return value is whatever cats returns, which is not clear from this snippet.

Imagine this in JavaScript terms as that language is a lot less ambiguous:

function get_non_red_cats(cats) {
  return cats(function(cat) {
    return !is_cat_red?(cat);
  }
}

Where this shows that cats is just a function that, potentially, takes a function. It might ignore your function, too.

Now if this is cats.each that changes things as that's probably the Enumerable each method which has well-defined behaviour.

In that case the return value is whatever cats is.

Upvotes: 1

Related Questions