Search code examples
rubylanguage-featuresenumerable

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 :)


Solution

  • 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.