Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I override the * method in Ruby?

Tags:

ruby

I'm working on a Ruby Gem for creating presentations, and I would like to create a syntax for defining slides that is simple and intuitive. I'm making use of instance_eval so I can call methods on self. Here's what I originally planned to do:

slide {
  title 'What is Ruby?'
  * 'a programming language'
  * 'with lots of interpreters'
  * 'lots of fun!'
}

Even though I've defined a * method, I get the error:

in `instance_eval': ... syntax error, unexpected '\n', expecting :: or '[' or '.' (SyntaxError)

I have compromised by creating a short method called b for creating bullets, but it's not as nice:

slide {
  title 'What is Ruby?'
  b 'a programming language'
  b 'with lots of interpreters'
  b 'lots of fun!'
}

Is this just a limitation of the interpreter? Or is there a way to get around it?

Update: If you want, you can dig into the full source code, but here is a small example of how it is implemented:

class Slide
  attr_accessor :title, :bullets
end

class SlidesDSL
  attr_accessor :slides
  def slide
    @slides ||= []
    s = SlideDSL.new
    s.instance_eval(&block)
    @slides << s.slide
  end
  class SlideDSL
    def slide
      @slide ||= Slide.new
    end

    def title(text)
      slide.title
    end

    def *(text)
      bullet(text)
    end

    def b(text)
      slide.bullets ||= []
      slide.bullets << text
    end
  end
end

# load_slides_from_file
source = File.read(filename)
dsl = SlidesDSL.new
dsl.instance_eval(source, filename, 0)
@slides = dsl.slides
like image 221
Andrew Avatar asked Jan 13 '23 13:01

Andrew


2 Answers

It seems that you are relying on syntactic sugar that is given to the * method for many things.

That's not the case. You can do this:

class Dog

  private
  def do_stuff(arg)
    puts 2 + arg
  end

end

d = Dog.new

d.instance_eval do
  do_stuff(3) 
end

--output:--
5
  1. instance_eval() changes self to its receiver, d in this case.
  2. In Ruby, private only means you cannot call the method with an explicit receiver.
  3. A method call without an explicit receiver implicitly uses self as the receiver.

Now if you change the method's name from do_stuff to *:

class Dog

  private
  def *(arg)
    puts 2 + arg
  end

end

d = Dog.new

d.instance_eval do
  *(3)
end

--output:--
1.rb:13: syntax error, unexpected '\n', expecting tCOLON2 or '[' or '.'

So the op is relying on normal method operation, not any syntactic sugar attributed to *. Inside the instance_eval block, you would expect Ruby to implicitly execute:

self.*(3)

which is equivalent to:

d.*(3)
like image 153
7stud Avatar answered Jan 26 '23 00:01

7stud


Yes, this is a limitation of the Ruby grammar. Specifically, you cannot, as sawa points out, use it with an implicit receiver (nor turn into a unary operator): it expects something on the left hand side.

All operators are simply methods called on the object referenced before it, but some operators are more equal than others. Most methods accept an implicit receiver, but the one named * doesn't.

I opted for o in a similar situation.

-- Added later (as I originally commented on 7stud's post):

The problem is that the Ruby parser (Yacc grammar + a bunch of methods) simply does not allow a line starting with a * to be parsed such that the * denotes a method call. If a line starts with a *, the only possible parsing is one where the * is the 'splat' operator. This limitation is unique to the * character used as a method name.

like image 38
Confusion Avatar answered Jan 26 '23 01:01

Confusion