Why can't I override the * method in Ruby? - 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

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
instance_eval() changes self to its receiver, d in this case.
In Ruby, private only means you cannot call the method with an explicit receiver.
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)

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.

If instead of using *, you are willing to use - (or +), you can achieve a very similar result by overloading the unary minus (or plus) operators on String. The following code automatically defines, and undefines unary - on string, so it works during the block but doesn't change any definition when the block is finished. Note that if a method is called inside the block, the - will still use the custom behaviour. This isn't much of an issue though, as there isn't a default definition for unary - for Strings. It could also be useful for extracting common code.
class Slide
attr_accessor :title, :bullets
end
class SlidesDSL
attr_accessor :slides
def slide(&block)
#slides ||= []
s = SlideDSL.new
old_unary_minus = String.instance_method(:-#) rescue nil
begin
String.send(:define_method, :-#) do
s.b(self)
end
s.instance_eval(&block)
ensure
String.send(:remove_method, :-#)
if old_unary_minus
String.send(:define_method, :-#) do
old_unary_minus.bind(self).call
end
end
end
#slides << s.slide
end
class SlideDSL
def slide
#slide ||= Slide.new
end
def title(text)
slide.title = text
end
def *(text)
bullet(text)
end
def b(text)
slide.bullets ||= []
slide.bullets << text
end
end
end
Example usage:
SlidesDSL.new.slide do
title "Some title"
- "one value"
- "another value"
4.times do |i|
- "repeated value #{i}"
end
end
Returns:
[#<Slide:0x007f99a194d0f8 #bullets=["one value", "another value", "repeated value 0", "repeated value 1", "repeated value 2", "repeated value 3"], #title="Some title">]

It looks like the * method has to be "anti-private". With the predefined method * on numerals, you can observe this.
3.instance_eval{*} # => syntax error, unexpected '}', expecting '='
3.instance_eval{*()} # => syntax error, unexpected '}', expecting :: or '[' or '.'
3.instance_eval{self.*} # => wrong number of arguments (0 for 1)
It looks like * is hard-wired to require an explicit receiver.

It seems that you are relying on syntactic sugar that is given to the * method for many things.
If you write your * method and use it with the implicit . to indicate it is a message to the receiver, you may have better luck.
For instance, when I do this with Fixnum
class Fixnum
def *
"This is the modified star"
end
end
When I try this in IRB
>> 1 * 1
ArgumentError: wrong number of arguments (1 for 0)
I would also get the normal problem of having it recognize the * if I were to simply press enter... it would expect more input on the next line...
However, if I do this:
>> 1.*
=> ""This is the modified star"
So it isn't that it is impossible to do, you may just be fighting with syntactic sugar that evolves around this particular symbol.
Consider the private method, which you can do, but you would have difficulties using the 'eval' family of calls. You could instead do this:
some_instance.send(:*)
If, of course, there is an argument needed, we can do some_instance.send(:*, argument)

Related

difference between dot(.) and double colon (::) in accessing class method

A double colon(::) allows constants, instance methods, and class methods defined within a class or module, to be accessed from anywhere outside the class or module.
Looking at this example:
class Sample
VAR_SAMPLE="what is the difference?"
def self.show_var
return VAR_SAMPLE
end
def method2
return VAR_SAMPLE
end
end
puts Sample::show_var # => what is the difference?
puts Sample.show_var # => what is the difference?
puts Sample::new::method2 # => what is the difference?
puts Sample.new.method2 # => what is the difference?
What is the difference in accessing class method using dot(.) and a double colon (::) operator then? Any thoughts are appreciated.
The double colon :: namespace operator can also be used as a message sending operator. In other words,
foo.bar
can also be written as
foo::bar
Except when not.
In particular, . is always a message send. :: is usually a namespace lookup, except when it cannot possibly be. That means, for example, you cannot call a message that starts with an uppercase character, unless you also pass an argument list.
foo = Class.new do
def BAR; :method end
BAR = :constant
end
foo.BAR #=> :method
foo::BAR #=> :constant
foo::BAR() #=> :method
The fact that :: can also be used for message sends is a historical curiosity, and is banned in most style guides except for "class factories", i.e. methods that return classes. Imagine a web framework that is set up like this:
module Controller
def self.R(path)
Class.new(AbstractController) do
# a bunch of methods for dealing with routing to `path`
end
end
end
class IndexController < Controller::R '/index.html'
def get
render 'Welcome'
end
end
In this case, in some style guides, it would be acceptable to write Controller::R because even though R is a method, it returns a class, so it kind-of acts like one.
But this is a special case for certain DSLs and is only allowed in certain style guides. Most style guides disallow :: for message sends, because it is redundant with ., because it already has a another different meaning (namespace resolution), and because it doesn't behave like . in all cases.
What is the difference in accessing class method using dot(.) and a double colon (::) operator then?
On the one hand, you can say, there is no difference because when used as the message sending operator, they both do the exact same thing.
On the other hand, there is a difference in syntax, namely that foo::BAR isn't a message send, it is a namespace lookup which is completely different. from foo.BAR, which is a message send.
You can call ruby methods using following ways
using Dot (.), Double Colon (::), send method & method method
class Sample
VAR_SAMPLE="what is the difference?"
def self.show_var
return VAR_SAMPLE
end
def method2
return VAR_SAMPLE
end
end
puts Sample::show_var
puts Sample.show_var
puts Sample.send(:show_var)
puts Sample.method(:show_var).call
puts Sample::new::method2
puts Sample.new.method2
puts Sample.send(:new).send(:method2)
puts Sample.method(:new).call.method(:method2).call
# All the above will return `what is the difference?` only
Now consider method which is a private
class Sample
VAR_SAMPLE="what is the difference?"
private
def self.show_var
return VAR_SAMPLE
end
end
puts Sample::show_var # Will throw error private method `show_var' called for
puts Sample.show_var # Will throw error private method `show_var' called for
puts Sample.send(:show_var) # what is the difference?
puts Sample.method(:show_var).call # what is the difference?
Note:- Other than this you can call ruby methods using other metaprogramming methods as well.

How to pass argument to the method definition of `[]`?

How can I pass an argument to the definition of a singleton method of [] on A, in other words A['some_argument']?
class A
def self.[]
# some_argument
end
end
Just as to any other method, using argument list:
class A
def self.[](arg)
puts arg
end
end
A[1]
# => 1
Good answers are already given, but I think they miss to mention an important point.
The method [], when used ordinarily in the form foo[params] is actually in syntax sugar form. The underlying method name is [], and calling it in the underlying form would be foo.[](params).
Syntax sugar plays around with syntax, and transforms a method call in the form foo[params] into foo.[](params). But that does not work in method definition, so you have to define such method in the underlying form, not in the syntax sugar form.
class A
def self.[](*args)
puts args
end
end
> A[1,2,3]
1
2
3
You can also implement "set" functionality
class A
def self.[]=(key, value)
puts "#{key} = #{value}"
end
end
A['one'] = '1'
# one = 1

Ruby: overload operator behaviour for some cases only

My question is: how do I overload an operator on a builtin class (such as Integer.new.+) but only for some cases, depending on the class of the second operand
This is the behaviour I'm looking for:
myObject = myClass.new
1 + myObject #=> special behaviour
1 + 2 #=> default behaviour (3)
For example, in Python I would define a __radd__ method on myClass to override case 1.
I've tried using super but apparently Numeric doesn't have operator methods.
Ideally, what I'm looking for is a way to extract the + method and rename it.
Like this:
class Integer
self.plus = self.+ # you know what i mean, I don't know how else to express this.
# I also know that methods don't work like this, this is just to
# illustrate a point.
def + other
other.class == myClass ? special behaviour : self.plus other
end
end
Thanks for your help
Both approaches posted here so far are a legacy Rails way, which is plain wrong. It relies on the fact that the class has no method called plus and nobody will reopen the class to create a method called plus. Otherwise things will go mad.
The correct solution is Module#prepend:
Integer.prepend(Module.new do
def + other
case other
when Fixnum then special_behaviour
else super(other)
end
end
end)
Yes, you can override the behavior of almost anything in the standard library to achieve an outcome, but that's going to hurt understanding of the code and come back to bite you sometime in the future.
In this particular case, Fixnum#+ is designed to take a numeric value and return a numeric result. If we want to define our own classes to interact with Fixnum#+, we need to understand the design contract and adhere to it.
The general convention in Ruby is to use duck typing. We don't care about the class of the object, we just care whether it behaves like / can be converted to the object we want. Eg:
class StringifiedNumber
def initialize(number)
#number = number
end
# String#+ calls to_str on any object passed to it
def to_str
# replace with number to string parsing logic
"one hundred"
end
end
> "total: " + StringifiedNumber.new(100)
=> "total: one hundred"
Things are a bit more complex with numbers since you may mix integers, floats, complex numbers, etc. The convention to handle this is to define a coerce method which returns two elements of the same type which are then used to perform the requested operation.
class NumberfiedString
def initialize(string)
#string = string
end
def to_i
# replace with complicated natural language parsing logic
100
end
def +(other_numberfied_string)
NumberfiedString.new(self.to_i + other_numberfied_string.to_i)
end
# For types which are not directly supported,
# Fixnum#+(target) will call the equivalent of
# target.coerce[0] + target.coerce[1]
def coerce(other)
[NumberfiedString.new(other.to_s), self]
end
end
> NumberfiedString.new("one hundred") + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc036d28 #string=200>
> 100 + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc824c88 #string="200">
To answer OP's follow up question:
Is there no equivalent to Python's radd and related methods? (Where,
if the first operand doesn't support the operation or the types, the
second operand takes over)
class MyClass
def +(other)
puts "called +"
end
def coerce(other)
[self, other]
end
end
> 1 + MyClass.new
called +
=> nil

Ruby Bracket Method with Block

I would like to define the [] method on a class of my own creation to take a block. I have done so as follows.
class A
def self.[](*args, &block)
puts "I am calling #{block} on #{args}."
block.(*args)
end
end
I can invoke this as follows.
# Explicit method invocation
A.[](1) { |x| puts x }
# With a procedure argument
arg = proc { |x| puts x }
A[2, &arg]
However, what I would like to be able to do is this.
A[3] { |x| puts x }
Which unfortunately seems to produce a syntax error. Is there a block syntax for the bracket method, or am I stuck with the first two ways of invoking it? In fact, more generally, which Ruby method names will allow blocks in their invocation, as it seems that there might be a limitation on when this is allowed?
There's not much you can do against a syntax error, so you'll have to change the syntax.
If you accept :
to define (i.e. pollute) an uppercase method inside Kernel (similar to Kernel#Array)
to use parens instead of brackets
You could write :
class A
def self.call_block_with_args(*args, &block)
puts "I am calling #{block} on #{args}."
block.call(*args)
end
end
module Kernel
def A(*args, &block)
A.call_block_with_args(*args, &block)
end
end
It works this way :
A(3) { |x| puts x }
#=>
# I am calling #<Proc:0x000000012b9c50#block_brackets.rb:14> on [3].
# 3
It's not clean, but it's probably the closest you can be to A[3] { |x| puts x }.
Blocks work with normal method calls only.
Ruby has plenty of operators, listing all of them here would be exhaustive, there are more than two dozens. Even `a` and !a and -a are method calls in Ruby. And obviously there are limitations to all these operators, eg + must take one parameter but not more, et cetera.
Fun fact, loop is a method call too.

How do I define a method on one line in Ruby?

Is def greet; puts "hello"; end the only way to define a method on one line in Ruby?
You can avoid the need to use semicolons if you use parentheses:
def hello() :hello end
No Single-line Methods
From rubocop/ruby-style-guide#no-single-line-methods:
Avoid single-line methods. Although they are somewhat popular in the
wild, there are a few peculiarities about their definition syntax that
make their use undesirable. At any rate - there should be no more
than one expression in a single-line method.
NOTE: Ruby 3 introduced an alternative syntax for single-line method
definitions, that's discussed in the next section of the guide.
# bad
def too_much; something; something_else; end
# okish - notice that the first ; is required
def no_braces_method; body end
# okish - notice that the second ; is optional
def no_braces_method; body; end
# okish - valid syntax, but no ; makes it kind of hard to read
def some_method() body end
# good
def some_method
body
end
One exception to the rule are empty-body methods.
# good
def no_op; end
Endless Methods
From rubocop/ruby-style-guide#endless-methods:
Only use Ruby 3.0's endless method definitions with a single line
body. Ideally, such method definitions should be both simple (a
single expression) and free of side effects.
NOTE: It's important to understand that this guideline doesn't
contradict the previous one. We still caution against the use of
single-line method definitions, but if such methods are to be used,
prefer endless methods.
# bad
def fib(x) = if x < 2
x
else
fib(x - 1) + fib(x - 2)
end
# good
def the_answer = 42
def get_x = #x
def square(x) = x * x
# Not (so) good: has side effect
def set_x(x) = (#x = x)
def print_foo = puts("foo")
P.S.: Just to give an up-to-date full answer.
def add a,b; a+b end
The semicolon is the inline statement terminator for Ruby
Or you can use the define_method method. (Edit: This one's deprecated in ruby 1.9)
define_method(:add) {|a,b| a+b }
Ruby 3.0.0 adds "endless" definitions for methods with exactly one statement:
def greet = puts("hello")
Note that the one-statement limitation means that this can't be written as:
# NOT ALLOWED
def greet = puts "hello"
SyntaxError: unexpected string literal, expecting `do' or '{' or '('
def greet = puts "hello"
^
It seems that this change was intended to either encourage the use of one-line methods or adjust to the reality that they are very common but hard to read -- "this kind of simple method definition [is estimated to] account for 24% of the entire method definitions" of the ruby/ruby code base.
Another way:
define_method(:greet) { puts 'hello' }
May be used if you don't want to enter new scope for method while defining it.
Yet another way:
def greet() return 'Hello' end

Resources