1

I was hacking a simple implementation of linked lists in Ruby and I came across something very peculiar.

I created a List class with an attr_accessor to a first_node. Something like this:

class List
  attr_accessor :first_node

  # Some code here ...
end

Then, while implementing the method delete_at I had the following error

linearly.rb:39:in `delete_at': undefined method `next' for nil:NilClass (NoMethodError) from linearly.rb:81:in `<main>'

This is a piece of the method:

def delete_at(position)
  if position == 0
    deleted_node = first_node
    first_node = first_node.next   # This is line 39.

    return deleted_node
  else
    # More code here ...

I forgot to use the class variable @first_node and instead I used the reader method first_node. Then, I started wondering why first_node returns nil when using first_node= in the same line.

Does it set @first_node to nil before setting a new value?

Notice that this piece of code works just fine:

def delete_at(position)
  if position == 0
    deleted_node = first_node
    first_node = deleted_node.next

    return deleted_node
  else
    # Some code here ...

EDIT:

This is how I call the implementation:

list = List.new
list.first_node = Node.new(1)
list.first_node.next = Node.new(2)
list.first_node.next.next = Node.new(3)

puts "Delete at 0"
puts list.delete_at(0)
Dagosi
  • 948
  • 2
  • 11
  • 25

2 Answers2

3

This:

first_node = first_node.next

creates a local variable called first_node. Variable declarations are hoisted to the top of the scope in Ruby so your method is equivalent to:

def delete_at(position)
  deleted_node = nil # Declare deleted_node and first_node
  first_node = nil   # as local variables.
  if position == 0
    deleted_node = first_node
    first_node = deleted_node.next

    return deleted_node
  else
    # Some code here ...

That means that all first_node references in your method will be the local first_node variable rather than the getter and setting methods that attr_accessor creates for you.

Either don't use variables that match method names or be explicit about what you mean by supplying the self receiver for the method calls:

def delete_at(position)
  if position == 0
    deleted_node = self.first_node
    self.first_node = deleted_node.next
    #...    
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • I disagree about these two methods being equivalent, but you were right about creating a local variable. – Dagosi Sep 08 '15 at 16:37
  • 1
    @Dagosi What's the difference between explicitly declaring and initializing the variables and letting Ruby do it for you? – mu is too short Sep 08 '15 at 17:03
  • @MuIsToShort there is no difference really, well, that depends on the value you assign when initializing the variables explicitly. This is a silly conversation, but what I meant was that when you declare explicitly `first_node = nil`, `deleted_node = first_node` assigns `deleted_node` to nil. Whereas, in my original code, `deleted_node = first_node` sets `deleted_node` to the first node in the `List`object. – Dagosi Sep 08 '15 at 18:15
  • I think you are not getting my point, but it doesn't matter anyways. – Dagosi Sep 08 '15 at 21:17
0

Are you sure @first_node has been set? Instance variables will return nil when unset.

class Test
  attr_accessor :foo
  def print_foo
    puts "@foo=[#{@foo}]"
    puts "foo=[#{foo}]"
    puts @foo.nil?
  end
end

t1 = Test.new
t1.foo # => nil
t1.print_foo  # prints "@foo=[]" and "foo=[]" and returns true
tgf
  • 2,977
  • 2
  • 18
  • 29
  • Yes, I'm setting `first_node` before calling `delete_at`. I think I forgot to mention that. I'll add it to the description. – Dagosi Sep 08 '15 at 01:38