Photo by Markus Spiske on Unsplash

Local variable scoping rules when Ruby converts a Proc into a block

I’m a student at Launch School and I’ve just completed my assessment for RB130, a course that covers closures, regression testing and packaging code in Ruby. During my studies I have been studying with other students, which has many benefits, one of which is gaining a greater depth of understanding of the fundamentals from dissecting code examples.

In one of these study sessions I was presented with the following code and asked, “without running this code, tell me what it will output and why?”.

I thought the code would output ["hi!!!", "bye!!!"] but I was incorrect. I’m going to step through my thought process and why it was incorrect.

My Thought Process

On line 8 a new Proc object was instantiated with a block and assigned to the local variable new_proc. On line 10 the exclaim method was invoked with two arguments passed in, an array of String objects and the Proc object, new_proc . On line 1 of the exclaim method the array of Strings was assigned to the parameter array andnew_proc was assigned to the parameter block. On line 3 of the exclaim method, map was called on array and an argument (&block) was passed in. The use of the unary operator & before a Proc object at method invocation transforms the Proc object into a block, so in my mind the code would function like this:

This would result in the map method iterating over each element within array and assigning the element to the block parameter item on line 3. Invoking yield with an argument item would result in method execution moving to the block on line 8, with the block parameter n being assigned the value of item. Within the block on line 8, the value of the object referenced by n is concatenated with the value of the object referenced by value.

Due to the local variable scoping rules in Ruby (i.e. local variables initialized in the outer scope are available in the inner scope but not vice versa) and because Ruby operates in a top-down manner, I thought the object referenced by the local variable value was the String '!!!' . This is where I was wrong.

The correct answer

The piece of the puzzle that I was missing is the fact that when Proc objects are converted into blocks by &, the block retains the binding of the Proc object.

Therefore, this is the correct way to describe what will happen in the first code example:

On line 6 the local variable value was initialized and assigned the String object '!!!' . On line 8 a Proc object was instantiated with a block { |n| n + value } and assigned to the variable new_proc. The variable value was in scope at the time of the Proc object instantiation, so value is part of the new_proc 's binding. On line 12 the exclaim method was invoked with two arguments, an array and the new_proc object. Within the exclaim method, new_proc is assigned to the parameter block, which is converted into a block and passed in to the map method. The map method will yield to the block, resulting in block execution. The conversion of a Proc into a block does not change the enclosure, therefore the block's bindings are the same as the Proc object's bindings. At the time of block execution, the block variable value references the String object '...' because value was re-assigned on line 10, before the exclaim method invocation. Therefore this code will return ["hi...", "bye..."]

Conclusion

I hope you have found this useful and if you’re thinking about studying web development or just want to improve your Ruby skills then I suggest you check out Launch School. The prep courses are free and it is the antithesis of all the “get rich quick”-esq bootcamps out there. It’s affordable, well supported by brilliant staff and focuses on creating developers with a rounded education.

Current life: studying web-development at Launch School. Previous life: PhD in molecular biology (UK), post-docs in USA and Australia.