A while ago I came across an interesting bit while handling variable bindings with Ruby for loops, namely that you can actually use a Constant as a for loop variable.
Now I'm working on developing a control flow graph for the annotated AST, and discovered another feature of for loop variables: you can use field syntax (also called accessor syntax):
Person = Struct.new(:name, :address) Address = Struct.new(:street, :city) my_house = Address.new('West St.', 'Hanover') shared_house = Address.new('Sachem Village', 'Lebanon') mike = Person.new('Mike', my_house) alice = Person.new('Alice', shared_house) bob = Person.new('Bob', shared_house) for mike.name, bob.address.city in [['James', 'Omaha'], ['Mike', 'Lebanon']] mike.address.city, christine.address.city = christine.address.city, mike.address.city end
What are the values of mike.address.city, alice.address.city, and bob.address.city after that for loop runs? I hope I'm not the only one who finds this nontrivial!
The confusing bit is a result of the fact that we're running extra code and mutating objects in the actual
for loop assignment. Really, just two methods Person#name= and Name#city= are being
called, but also bob.address is another method call during the assignment, and it
could even itself do anything confusing such as cause a mutation! For example, if instead we defined Person#address as:
Person = Struct.new(:name, :address) do def address (@counter ||= 0) += 1 address.street *= counter super end end
Now each iteration of the above loop mutates the incoming variable differently with each iteration of the loop, in the middle, non-assigning method!
It was easy to say I thought constants didn't belong in the for loop variable list. This is
slightly different as it might make sense for some reason, perhaps with mock objects in a test
running loop. It still seems disastrous to dance with. Laser, the static analyzer and linter
that is the subject of my undergraduate thesis, will warn if it discovers Ripper :field
nodes are discovered in the a loop variable list.
Personal Opinion: Mutation
This seems like a point where I might note that a recent discussion on Ruby-Talk (379527) touched on, and one which Laser will have to make a choice: to what convention should Rubyists assign the use of bang (!) at
the end of a method name? The standard library is fraught with inconsistencies, with expert advice suggesting it primarily indicate in-place mutation,
but also sometimes indicating irreversible actions, and return values (gsub! comes to mind)
are also often inconsistent. Some have suggested that ! should be used
to indicate mutation in new code, with others dissenting in favor of it indicating general dangerousness.
I personally think knowing precisely which methods cause a mutation lexically, namely if they end in = or !, can be a useful
convention. I think the fact that I could modify the address when the bob.address portion of bob.address.city= executed indicates
disaster if the method I had called did some other mutation.
I'll be adding to Laser a simple regex setting that must pass on mutating method names, with a quick option for deactivation from
the command-line. It will default to /[!=]$/.

Enjoy this article? Then feel free to: