A Definition Of Magic
Magic is an intangible quality of a codebase. Sometimes it’s amazing, allowing you to implement complicated functionality in just a few lines of code. Other times it’s scary, confusing, or frustrating.
Rails and Ruby are often accused of being “too magical,” but what does that mean, exactly? Here, I’d like to make the term more tangible by putting forward a definition: magic is implicitness without understanding.
Implicitness
Something is implicit when it is implied, but not directly expressed. A simple example of this is the implicit receiver for Ruby method calls. For example, this:
puts("hello")
is equivalent to this:
self.puts("hello")
These two bits of code are not strictly equivalent, due to the way that Ruby implements access modifiers.
A more correct equivalent would be self.send(:puts, "hello")
.
The receiver – the object which the method is being called upon – does not need to be specified explicitly when it is self
.
This is implicitness.
Another closely-related form of Ruby implicitness is the self
argument.
It may sound strange to refer to self
as an argument, since it never appears in an argument list.
But if you look under the hood at the YARV instructions, self
is passed into every method call in the same way that the arguments are.
You can think of self
as an implicit first argument to each and every method call.
Compare this with Python, where self
is an explicit argument to every method:
class Animal:
def __init__(self, noise):
self.noise = noise
def speak(self):
print "{0} {0}!".format(self.noise)
dog = Animal('WOOF')
dog.speak() # prints: WOOF WOOF!
This is one reason why Ruby is considered to be more magical than Python.
In Ruby, self
is mostly implicit, but in Python it is explicit.
Understanding
At this point you may be wondering how something as fundamental as a method call could be considered magic. Implicitness, by itself, is not sufficient. Implicit code only becomes magic when it is used by someone who doesn’t understand it.
Magic is a point of view. While implicitness is objective, a person’s level of understanding is subjective. Every programmer has a different skill level, and a different amount of familiarity with any given codebase. Things that seem like crazy voodoo to a beginner may be completely obvious to a senior developer.
A dove appearing from a hat is only magical if you don’t know how it was possible. It’s not at all magical to the person who purchased the specially-made hat, trained the doves, and shoved them into the hat before the show. Magic ceases to be magical when you know how it works.
This is why Aaron Patterson, one of the most experienced Rails developers in the world, has trouble seeing the magic in Rails.
I always read about Rails having "too much magic" but people don't give specific examples. Are there any blog posts I can read?
— Aaron Patterson (@tenderlove) January 19, 2016
So, let’s revisit the question: how can something as fundamental as a method call be considered magic?
Answer: when the developer has a limited understanding of the programming language.
To a beginner, Ruby is full of magic.
It’s not obvious that a method is being called upon self
.
Method calls look like magic words – you type in the magic word render
and a web page appears.
When you learn more about Ruby, and the architecture of Rails, the magic disappears.
Choosing Between Implicit And Explicit
It is a waste of time to argue whether some piece of code is magic or not. There is no such thing as objectively magical code, as I have tried to argue above. The real issue is that of implicit versus explicit.
Like all other programming concepts, this is not a moral choice. Explicitness is not good, and implicitness is not bad, or vice versa. They just pose different trade-offs, with pros and cons on each side.
Choosing implicitness gives you conciseness. It removes boilerplate and cruft, leaving behind the most important bits. It allows you to achieve more with less code.
The price you pay for implicitness is a steeper learning curve. It’s harder to work with the code if you don’t already understand it.
On the other hand, explicitness gives you straightforward code. The verbosity allows people to follow the code more easily if they haven’t worked with it before. It’s plain and predictable.
Verbosity is also the downside of being explicit.
You have to write more repetitive boilerplate code, and the boilerplate can obscure the important bits.
I know that a bunch of Rubyists were reading the Python code earlier, frowning inwardly as they imagined writing self
in every single argument list.
It is a sliding scale, not a choice between two extremes. Pick a level that is suitable for you, your team, and all the developers that will be using the code.
Got questions? Comments? Milk?
Shoot an email to [email protected] or hit me up on Twitter (@tom_dalling).