Elmord's Magic Valley

Computers, languages, and computer languages. Às vezes em Português, sometimes in English.

Some notes on Ruby #1: method definition and calls

2024-06-14 21:17 +0100. Tags: comp, prog, pldesign, ruby, in-english

[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.

All calls are method calls

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:

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

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!"

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

  <... your code goes here ...>

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

class DeepThought
  def compute_answer()
    multiply(6, 7)

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!

Comentários / Comments (0)

Deixe um comentário / Leave a comment

Main menu

Recent posts

Recent comments


em-portugues (213) comp (152) prog (74) in-english (66) life (49) pldesign (40) unix (39) lang (32) random (28) about (28) mind (26) lisp (25) fenius (22) mundane (22) web (20) ramble (18) img (13) hel (12) rant (12) privacy (10) scheme (10) freedom (8) copyright (7) bash (7) esperanto (7) academia (7) lash (7) music (7) shell (6) mestrado (6) home (6) misc (5) emacs (5) android (5) conlang (5) worldly (4) php (4) book (4) editor (4) latex (4) etymology (4) politics (4) c (3) tour-de-scheme (3) network (3) film (3) kbd (3) ruby (3) wrong (3) security (3) llvm (2) poem (2) wm (2) cook (2) philosophy (2) treta (2) audio (2) comic (2) x11 (2) lows (2) physics (2) german (1) ai (1) perl (1) golang (1) translation (1) wayland (1) en-esperanto (1) old-chinese (1) kindle (1) pointless (1)


Quod vide

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.