Ruby is NOT a Callable Oriented Language (It's Object Oriented)
I recently ran across a presentation entitled Python vs. Ruby: A Battle to the Death. I didn't consider it to be a particularly fair battle, and may well reply in more detail in a later post.
However, what struck me as most worthy of explanation was the presenter's concern about the fact that Procs are not callable via parens.
x = Proc.new { puts "HELLO" }
x() #=> undefined method `x' for #<Object:0x1001bd298><
x.call #=> "HELLO"
x[] #=> "HELLO"
For those coming from a callable-oriented language, like Python, this seems horribly inconsistent. Why are methods called with ()
, while Procs are called with []
.
But what's going on here is that Ruby doesn't have a notion of "callable", like Python. Instead, it has a pervasive notion of Object. Here, x
is an instance of the Proc class. Both call
and []
are methods on the Proc class.
Designing for the Common Case: Calling Methods
Coming from a callable-oriented language, this might seem jarring. But Ruby is designed around Objects and the common cases of working with objects.
Calling methods is far more common than wanting to get an instance of Method, so Ruby optimizes the case of calling methods, with a slightly less elegant form to access an instance:
class Greeter
def say
puts "Hello world!"
end
end
Greeter.new.say #=> "Hello world!"
Greeter.method(:new) #=> #<Method: Class#new>
Greeter.new.method(:say) #=> #<Method: Greeter#say>
# This is so that you don't have to say:
Greeter.new().say()
Ruby considers the common case of calling methods, and optimizes that case while still making the less common case possible.
Designing for the Common Case: How Blocks Are Really Used
One of the reasons that the Proc.new
case throws off Rubyists in debates is that Rubyists literally never call Proc
objects using []
.
In Ruby, Procs are the object passed to methods when using block syntax. Here is how Proc
s are actually used:
def hello
puts "Hello world!"
yield
puts "Goodbye cruel world!"
end
hello { puts "I am in the world!" }
When examining languages that support passing anonymous functions to functions (like JavaScript), it turns out that the vast majority of such cases involve a single anonymous function. As a result, Matz (inspired by Smalltalk) built in the idea of a block as a core construct in Ruby. In the vast majority of cases, blocks are created using lightweight syntax ({}
or do/end
) and called using yield
.
In some cases, blocks are passed from one method to the next, before they are finally called using yield:
def step1(&block)
puts "Step 1"
step2(&block)
end
def step2
puts "Step 2"
yield
end
step1 { puts "Do the action!" } #=> "Step 1\nStep 2\nDo the action!"
As you can see, Ruby builds in the idea of calling a block into the core language. I searched through Rails (a fairly large codebase) for instances of using []
to call a Proc and while we use blocks extremely commonly, we don't use []
to call them.
I suspect that the reason this comes up is that people who are used to having to define standalone functions, pass them around, and then call them are looking for the analogous constructs in Ruby, but are missing the different paradigm used by Ruby.
Consistent Method Execution
Fundamentally, the issue here comes down to this:
def foo
proc {}
end
foo()
In Ruby, methods are invoked with our without parentheses. All methods return values, which are always Objects. All Objects have methods. So foo()
is a method call that returns a Proc object. It's extremely consistent, with very few axioms. The fact that the axioms aren't the same as those in a callable-oriented language doesn't make them "weird".