duykhoa
duykhoa

Reputation: 2302

What's wrong with my ruby lazy evaluation

Here is my simple program to demonstrate the lazy evaluation concept.

class A
  def x
    y += 1
  end

  def y
    @y ||= 0
  end
end

A.new.x

But I got this result when running this program

NoMethodError: undefined method `+' for nil:NilClass

Am I doing something wrong?

When @y is an array, thing works perfectly.

class A
  def x
    y << rand(10)
  end

  def y
    @y ||= [] 
  end
end

a = A.new
a.x

UPDATE

Finally I understand the problem. Ruby is pass-by-value. When I call method y in the first example, Ruby doesn't care about the instance_variable, it copies and returns the value of @y only.

In the second example, Ruby still copies and returns the value of @y variable. But in this case, @y is a pointer of the real array, and the copy of this pointer still points to the same array.

But why the error is undefined method + for nil, I expect that the y + 1 which y equal to the value of @y (0). So why it returns nil for y in this case?

Upvotes: 0

Views: 418

Answers (1)

spickermann
spickermann

Reputation: 106882

It is important to understand what y += 1 really does. y += 1 is the equivalent to:

y = y + 1

That means you just told Ruby you want to assign y + 1 to the local variable y. Because Ruby prefers to read from local variables over calling methods, it uses the current value of the local variabel y and tries to add 1. But the local variable y is still nil at this moment, therefore this operation raises a undefined method '+' for nil:NilClass exception.

I guess you expected y += 1 to call the method y, add 1 and write result back to @y. To achieve this you have to change your code a bit to:

class A
  attr_writer :y

  def x
    self.y += 1
  end

  def y
    @y ||= 0
  end
end

a = A.new
a.x
#=> 1
a.x
#=> 2

self.y ensures that you read the method y and do not create a local variable. Furthermore you need a setter method attr_writer to be able to call y =.

Why does your second example work out of the box? Because << isn't a shortcut that creates a local variable, therefore it receives the array from y. And << shifts a value into that array in place without the need to call a setter method like y =.

Interesting read in this context: What Ruby’s ||= (Double Pipe / Or Equals) Really Does

Upvotes: 5

Related Questions