Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Ruby Enumerable#map (with more complex blocks)

Let's say I have a function

def odd_or_even n
  if n%2 == 0
    return :even
  else
    return :odd
  end
end

And I had a simple enumerable array

simple = [1,2,3,4,5]

And I ran it through map, with my function, using a do-end block:

simple.map do
  |n| odd_or_even(n)
end
# => [:odd,:even,:odd,:even,:odd]

How could I do this without, say, defining the function in the first place? For example,

# does not work
simple.map do |n|
  if n%2 == 0
    return :even
  else
    return :odd
  end
end

# Desired result:
# => [:odd,:even,:odd,:even,:odd]

is not valid ruby, and the compiler gets mad at me for even thinking about it. But how would I implement an equivalent sort of thing, that works?

edit

In reality, the solution to my problem matters to me a lot less than the motivation/reasoning behind it, to help me understand more how ruby blocks work :)

like image 810
Justin L. Avatar asked Jun 15 '10 01:06

Justin L.


People also ask

What does enumerable mean in Ruby?

Enumeration refers to traversing over objects. In Ruby, we call an object enumerable when it describes a set of items and a method to loop over each of them.

What is an enumerable method?

Enumerable methods work by giving them a block. In that block you tell them what you want to do with every element. For example: [1,2,3].map { |n| n * 2 } Gives you a new array where every number has been doubled.

Is array enumerable Ruby?

Some Ruby classes include Enumerable: Array. Dir. Hash.

What does the lazy method do to enumerators and why is that useful?

Enumerator::Lazy is a special type of Enumerator , that allows constructing chains of operations without evaluating them immediately, and evaluating values on as-needed basis. In order to do so it redefines most of Enumerable methods so that they just construct another lazy enumerator.


1 Answers

You're so close. Just remove the returns and you're golden.

This is because the block passed to map is a proc (i.e. created with Proc.new), and not a lambda. A return within a proc doesn't just jump out of the proc- it jumps out of the method that executed (i.e. called call on) the proc. A return within a lambda, on the other hand, jumps out of only the lambda.

The proc method returns a lambda in Ruby 1.8, and a Proc in Ruby 1.9. It's probably best to just not use this method and be explicit with which construct you want to use.

I'm guessing you were either in IRB or a plain ruby script when you were trying this out.

a = Proc.new { return }
a.call # fails. Nothing to return from.

def foobar
  a = Proc.new { return }
  a.call
  puts 'hello' # not reached. The return within the proc causes execution to jump out of the foobar method.
end
foobar # succeeds, but does not print 'hello'. The return within the proc jumps out of the foobar method.

b = lambda { return }
b.call # succeeds. The return only returns from the lambda itself.

def bazquux
  b = lambda { return }
  b.call
  puts 'hello' # this is reached. The lambda only returned from itself.
end
bazquux # succeeds, and prints 'hello'

The lesson to learn from this is to use implicit returns unless you can't, I guess.

like image 108
x1a4 Avatar answered Sep 27 '22 22:09

x1a4