[This post is part of a series on Ruby semantics.]
I’ve been studying Ruby recently for a job opportunity. The job did not pan out in the end, and therefore I’ll probably not continue with my Ruby studies, but I want to write down some things I learned before I forget them.
The focus of these notes is not on how to use the language, but rather on how it works, i.e., the language semantics. This may end up making the language seem weirder than it actually is in practice, because a lot of the examples will be dealing with corner cases. I will be writing this from a Python (and sometimes Lisp) perspective.
Functions and methods, though superficially similar, work very
differently in Python and Ruby. In both languages, x.f(a)
mean “call method f
of object x
with argument
a
”, but it works quite differently behind the scenes:
x.f
evaluates to a bound
method object, and then that function-like object is called with
a
as an argument.f
with argument
a
to object x
. There is no intermediate bound
method object.In Ruby, x.f
on its own is equivalent to
x.f()
, i.e., send the message f
with no
arguments to object x
. In general, parentheses can be
omitted from method calls if there is no ambiguity.
f()
on its own is equivalent to self.f()
.
Whereas in Python, self
is an argument of the function that
implements a method and has to be defined explicitly, in Ruby
self
is a keyword that refers to the current object and is
always available.
Likewise, def
always defines a method. Whereas in Python
def
defines a function in the local scope, in Ruby
def
defines a method in the current class. So, for
example:
class Foo def g def h 42 end end end
This defines a class Foo
with a method g
,
which, when called, defines method h
in class
Foo
. So, afterwards:
irb(main):008:0> x = Foo.new => #<Foo:0x00007f72a83dd3b8> irb(main):009:0> x.h # Method does not exist yet. (irb):9:in `<main>': undefined method `h' for #<Foo:0x00007f72a83dd3b8> (NoMethodError) from /usr/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>' from /usr/bin/irb:25:in `load' from /usr/bin/irb:25:in `<main>' irb(main):010:0> x.g # When g is called, h is defined. => :h irb(main):011:0> x.h # Now h exists. => 42
(In this sense, Python’s def
is more like Scheme’s
define
, whereas Ruby’s def
is more like Common
Lisp’s defun
or defmethod
.)
If a new Foo
object is instantiated now, it will have
access to method h
already, since the def h
defined it in the class Foo
, not in the instance
x
:
irb(main):012:0> y = Foo.new => #<Foo:0x00007f72a842a3c0> irb(main):013:0> y.h => 42
What if you use def
at the top-level outside of a class?
Well, in that case, self
refers to the main
object, which is an instance of Object
(the base class of
most Ruby classes). So a method defined at the top-level is a method of
Object
! For example, let’s define a hello
method with no arguments (again, the parentheses around the arguments
can be omitted):
def hello puts "Hello, world!" end
And now we can call it:
irb(main):019:0> hello Hello, world! => nil
But since the method was defined as a method of Object
,
won’t it be available on every object?
irb(main):020:0> 4.hello (irb):20:in `<main>': private method `hello' called for 4:Integer (NoMethodError) from /usr/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>' from /usr/bin/irb:25:in `load' from /usr/bin/irb:25:in `<main>'
Note that the call fails not because the method is not defined, but
because the method is private. We can override the access
control by using the send
method to send the message
explicitly to the object:
irb(main):021:0> 4.send(:hello) Hello, world! => nil
And there we go. The code at the top-level effectively runs as if it were inside a:
class Object private <... your code goes here ...> end
Note how code that looks superficially like Python and seems to work the same way is actually doing so by very different means. For example, consider a piece of code like:
def multiply(x, y) x * y end class DeepThought def compute_answer() multiply(6, 7) end end puts DeepThought.new().compute_answer() # prints 42
The method compute_answer
uses the multiply
method defined at the top-level. In Python, the equivalent code works by
searching for multiply
in the current environment, finding
it at the global scope, and calling the function bound to it. In Ruby,
this works by defining multiply
as a method of
Object
, and because DeepThought
inherits from
Object
by default, it has multiply
as a
method. We could have written self.multiply(6, 7)
and we
would get the same result.
This means you can easily clobber someone else’s method definitions
if you define a method at the top-level. I guess it’s okay to do that if
you’re writing a standalone script that won’t be used as part of
something bigger, but if you’re writing a library, or a piece of a
program consisting of multiple files, you probably want to wrap all your
method definitions within a class
or module
definition. I plan to talk about those in a future blog post.
See you next time!
Copyright © 2010-2024 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.
Powered by Blognir.