jojo
jojo

Reputation: 1145

Implicit return value scope in Ruby

I'm using a self-defined include? method to examine the difference between explicit and implicit returns. I understand that #each returns the collection iterated on, so I believe that I need to place my true/false implicit returns in the correct place, but when I do I am getting back the collection and I'm not sure what to modify.

def self.include?(array, search_item)
  array.each do |elem|
    if elem == search_item
      true
    end
  end
end

Below are the tests I'm checking, but I'm not understanding how to match the returns correctly. Why are they not matching or how should I understand the scope of the implicit return?

  result = MethodReturns.include?(numbers_array, 4)
  expect(result).to eq(true)

  result = MethodReturns.include?(numbers_array, 7)
  expect(result).to eq(false)

Upvotes: 0

Views: 546

Answers (3)

Uri Agassi
Uri Agassi

Reputation: 37409

If you use explicit return inside your each block - reaching that code will result in immediately returning the value (stopping the iteration). This is because of the non local return feature of blocks:

Ruby’s blocks support non-local-return, which means that a return from the block behaves identically to returning from the block’s original context.

So your code should work properly if you simply use explicit return:

def self.include?(array, search_item)
  array.each do |elem|
    if elem == search_item
      return true
    end
  end
end

Upvotes: 2

7stud
7stud

Reputation: 48599

The LAST statement executed provides the return value. The last thing that happens in an each() loop will always be the ending of the loop(unless you break out of the loop somehow, e.g. return, break):

def do_stuff()
  [1, 2, 3]. each do |num|
    true if num == num
  end
end

p do_stuff

--output:--
[1, 2, 3]

So your true value gets thrown away, and then the iteration with each() continues until the each() loop ends, and as you know, when an each() loop ends it returns the left hand side.

Here's an example of a method that implicitly returns true:

def do_stuff()
  [1, 2, 3]. each do |num|
    #blah
  end

  true
end

The each() method still returns the array--but the each() method is not the LAST statement that executes in the do_stuff() method.

In your example, you could do this:

def do_stuff(target)
  found = false

  [1, 2, 3]. each do |num|
    found = true if num == target
  end

  found
end


p do_stuff(2)

--output:--
true

However, that solution wastes resources because the each() method continues iterating over the Array after a match is found. For instance, if your Array had 1 million elements in it, and a match was found at index 0 in the Array, the each() method would needlessly continue iterating over the remaining 999,999 elements. On the other hand, returning explicitly when a match is found will terminate the loop.

It is never a mistake to explicitly write return val. As you get more experienced, you can drop the return portion--WHEN APPROPRIATE--to show the world you know ruby...however every now and then omitting return will still trip you up.

Upvotes: 3

Tacoman667
Tacoman667

Reputation: 1401

You will want to change to:

def self.include?(array, search_item)
  array.each do |elem|
    if elem == search_item
      return true
    end
  end
  return false
end

The reason for this is because the last statement in the method is the return value. In your case you never return false if there is no match. Be sure to add the return when you want to break out and stop the rest of the method execution immediately.

Upvotes: 3

Related Questions