Thursday, May 25, 2017

Learning Ruby

I'm trying to learn Ruby and thought a good way to learn the basics would be to dust off my algorithms book and try to code various algorithms in Ruby.  As I dived into it, I felt that joy that comes with trying out something new and getting it to work.  There's something about seeing the familiar written out in a new way that gets your pulse racing, that creates its own motivation to go further.

 I started with the insert sort and came up with the following:

# array filled with random numbers
a = Array.new(20).map! { Random.rand(50) }

# sort the array
a.each.with_object([]) do | value, asort |
  head = asort.take_while { |value2| value2 < value }
  tail = asort[head.length...asort.length]
  asort = head + [value] + tail
end

It's probably inefficient, but it should work, right?  Instead, it returns an empty array for any input. Trying out the contents of the block in irb works as it should, but adding puts to the block shows that it starts with an empty array each time it's called.  So here I hit a wall.  As much as starting out was a high of discovery, this was a low of frustration.  I cycled between disappointment and anger, between wondering why I wasn't getting it, and wondering why this language wasn't doing what I expected. Instead of giving up, I did some frustrated googling and fiddling in the irb, and finally discovered that the following does work:

asort = []
a.each do |value|
  head = asort.take_while { |value2| value2 < value }
  tail = asort[head.length...asort.length]
  asort = head + [value] + tail
end

It looks almost exactly the same.  So why does the second one work but the first fail?  It looks like with_object just doesn't work.

It's a subtle point, and these subtle points are exactly why you learn a language better if you write code than if you just read a book.  In the last line of the block, the asort array gets reassigned.  When the array is reassigned, the symbol asort points to the new array created.  This works fine in the second version because asort is part of a closure.  The symbol asort is part of the enclosing scope outside the block, so the reassignment is carried over to the next iteration of the block.  But in the first version, asort isn't part of a closure; it's just a local variable.  It goes out of scope at the end of the block, so the array created by the assignment disappears.  The with_object method yields the originally provided array to the block, which continues to be empty because no code has actually modified it.

Because of this, when you're using with_object you can only use methods that change the underlying object.  Luckily, the documentation for Ruby makes this easy to spot.  A method that creates a new array will look as follows:


while a method that acts on the underlying array will appear like this:


The difference is after the arrow.  The notation new_ary means a new array will be created, while
ary means the underlying array will be changed.  Close by these two methods is one that can solve our problem. 

If we really need to do a reassignment inside a block, we can use replace.  If we put that method call in the original code, everything works.

a.each.with_object([]) do | value, asort |
  head = asort.take_while { |value2| value2 < value }
  tail = asort[head.length...asort.length]
  asort.replace head + [value] + tail
end

We've learned something about Ruby, but since this blog is about the psychology of programming as well, I want to talk about that too.  The feelings I went through - initial elation, frustration and doubt, and then a final resolution into a higher understanding - is a pattern.  It's the pattern that all learning takes.  Way back when I was an undergraduate learning mathematics it happened so often I got bored with it (here's the part where I'm frightened and confused by the new notation, ho hum.)  For the learning to take place, you have to go through the whole cycle.  If you don't keep pushing when you feel confused, you'll never get to that final resolution, and however fun that initial joy is, the higher understanding just feels more satisfying.  It's as good as the final chord in a piece of music, or the end of a novel when everything comes together.  That cycle of feelings is why learning is so satisfying, and why you should keep doing it as long as you can.