Overloading :+= in Ruby - ruby

I want to define a custom :+= method on my Ruby class that modifies the instance. However, the following code raises syntax error, unexpected '=', expecting ';' or '\n'
def +=(other)
#value += other.to_f
end
What are my options here? I see that Fixnum has :+# and :-# methods, but I don't exactly see what they do in the documentation. Are those the methods I want to write?

Unlike C++ or other languages with robust overrides, there's no such method as += in Ruby.
What's happening internally is a sort of expansion. For example:
x += n
If x is a variable, then this is equivalent to:
x = x.send(:+, n)
If x= is defined, then this is equivalent to:
send(:x=, x.send(:+, n))
Whatever you need to do to override must be to redefine x= or + on the class of x.
Remember that the + method should not modify x. It's supposed to return a copy of x with the modification applied. It should also return an object of the same class of x for consistency.
Your method should look like:
def +(other)
# Create a new object that's identical in class, passing in any arguments
# to the constructor to facilitate this.
result = self.class.new(...)
result.value += other.to+f
result
end

I am not really sure, but you do not need the = operator.
def +(other)
#value += other.to_f
end
Example from here:
class Money
attr_accessor :value
def +(b)
result = dup
result.value += b.value
return result
end
def -(b)
result = dup
result.value += b.value
return result
end
end

Related

Difference between self.element = 'this' and self.send("element=", 'this')

I am trying to understand why these two things return different values.
Value is a string, and field is a text_field.
def populate_text(field, value)
self.send "user_name=", value
end
# => nil
def populate_text(value)
self.user_name = value
end
# => "value"
Why do self and send have different return values?
This class includes PageObject if that helps.
Ruby's syntax sugar for calling methods whose name ends with = always returns the righthand value, regardless of the return value of the method.
This is not the case when you use send to invoke the method. For example:
class Foo
def bar=(n)
:ohno
end
end
f = Foo.new
x = (f.bar = 42)
y = f.send("bar=", 42)
p [x,y]
#=> [42, :ohno]
So, you would get two different values if your user_name= method has a return value that is not the argument to the method.
Self.Send allows you to dynamically choose your objects, regardless of type.
This lets you data drive your test with very simple code.

referring attributes in Ruby

class Account
attr_accessor :balance
def initialize (amount=0)
self.balance = amount
end
def -(x)
self.balance -= x
end
def +(x)
self.balance += x
end
def to_s
puts "Balance is #{self.balance} !!!!!!!!!"
end
end
acc = Account.new(20)
acc -=5
Can someone explain why it works to write: puts acc (render 15 as a result) but this line:
puts acc.balance doesn't work (it says undefined method balance, however it's not a method, it should be seen as a property/attribute)
Thanks in advance
acc -= 5 is equivalent to acc = acc - 5.
acc - 5 is 15 (by Account#- method); acc = acc - 5 is like acc = 15.
Now acc is Fixnum object.
How about implement +, - as follow?
def -(x)
Account.new(self.balance - x)
end
def +(x)
Account.new(self.balance + x)
end
The source of your error is line
acc -= 5
it assings result (returned value) of substraction method to acc variable, and this result is Fixnum instance, which doesn't have balance method defined.
If you want your + and - methods to return Account instance, you need to specify this:
def +(x)
self.balance += x
self
end
etc.
BTW, accessors in Ruby ARE methods.
You want to edit your two methods to make += and -= work, so that they return the object in place:
def -(x)
self.balance -= x
self
end
def +(x)
self.balance += x
self
end
That way, the object it reassigned to itself.
Oops, there is a number of things that have been wrong with this one. I cannot explain all the problems, but let's see how far I can get:
First of all you have redefined the #+ and #- methods to give them semantics you would expect from #+= and #-=. Given this: if you use acc-5 in your example (instead of acc-=5) the behavior is close to what you expect, irb will display 15 as this is the result of your #- method (which has the side effect of subtracting 5 of balance as well).
However, as you call #-= in your example the ruby interpreter ends up assigning the methods result Fixnum 15 to your acc-variable. This will give you the error message NoMethodError: undefined method 'balance' for 15:Fixnum when you try to access #balance which happens to be defined on your Account class.
Your #- method should return an Account-object, not change the existing state of the object and return the result as a Fixnum. See http://ruby.about.com/od/oo/ss/Overloading-Operators.htm for examples on how to redefine operators. I understand that this somehow defeats your purpose as you would have liked to use #-= to modify the object in place, but it only works by creating another Account-instance in Ruby as you are not able to redefine #-= directly (see the link above).
Of course you are free to define a #withdraw-method to get the semantics you desire.

Change the binding of a Proc in Ruby

I have this code:
l = lambda { a }
def some_function
a = 1
end
I just want to access a by the lambda and a special scope which has defined a already somewhere like inside some_function in the example, or just soon later in the same scope as:
l = lambda { a }
a = 1
l.call
Then I found when calling l, it is still using its own binding but not the new one where it was called.
And then I tried to use it as:
l.instance_eval do
a = 1
call
end
But this also failed, it is strange that I can't explain why.
I know the one of the solution is using eval, in which I could special a binding and executing some code in text, but I really do not want to use as so.
And, I know it is able to use a global variable or instance variable. However, actually my code is in a deeper embedded environment, so I don't want to break the completed parts if not quite necessary.
I have referred the Proc class in the documentation, and I found a function names binding that referred to the Proc's context. While the function only provided a way to access its binding but cannot change it, except using Binding#eval. It evaluate text also, which is exactly what I don't like to do.
Now the question is, do I have a better (or more elegant) way to implement this? Or using eval is already the regular manner?
Edit to reply to #Andrew:
Okay, this is a problem which I met when I'm writing a lexical parser, in which I defined a array with fixed-number of items, there including at least a Proc and a regular expression. My purpose is to matching the regular expressions and execute the Procs under my special scope, where the Proce will involved some local variables that should be defined later. And then I met the problem above.
Actually I suppose it is not same completely to that question, as mine is how to pass in binding to a Proc rather than how to pass it out.
#Niklas:
Got your answer, I think that is what exactly I want. It has solved my problem perfectly.
You can try the following hack:
class Proc
def call_with_vars(vars, *args)
Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
end
end
To be used like this:
irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4
This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:
l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1) # => 4
Using the following, more complex hack, this exact behaviour can be achieved:
class LookupStack
def initialize(bindings = [])
#bindings = bindings
end
def method_missing(m, *args)
#bindings.reverse_each do |bind|
begin
method = eval("method(%s)" % m.inspect, bind)
rescue NameError
else
return method.call(*args)
end
begin
value = eval(m.to_s, bind)
return value
rescue NameError
end
end
raise NoMethodError
end
def push_binding(bind)
#bindings.push bind
end
def push_instance(obj)
#bindings.push obj.instance_eval { binding }
end
def push_hash(vars)
push_instance Struct.new(*vars.keys).new(*vars.values)
end
def run_proc(p, *args)
instance_exec(*args, &p)
end
end
class Proc
def call_with_binding(bind, *args)
LookupStack.new([bind]).run_proc(self, *args)
end
end
Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:
l = lambda { |a| local + func(2) + some_method(1) + var + a }
local = 1
def func(x) x end
class Foo < Struct.new(:add)
def some_method(x) x + add end
end
stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)
p stack.run_proc(l, 5)
This prints 15, as expected :)
UPDATE: Code is now also available at Github. I use this for one my projects too now.
class Proc
def call_with_obj(obj, *args)
m = nil
p = self
Object.class_eval do
define_method :a_temp_method_name, &p
m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
end
m.bind(obj).call(*args)
end
end
And then use it as:
class Foo
def bar
"bar"
end
end
p = Proc.new { bar }
bar = "baz"
p.call_with_obj(self) # => baz
p.call_with_obj(Foo.new) # => bar
Perhaps you don't actually need to define a later, but instead only need to set it later.
Or (as below), perhaps you don't actually need a to be a local variable (which itself references an array). Instead, perhaps you can usefully employ a class variable, such as ##a. This works for me, by printing "1":
class SomeClass
def l
#l ||= lambda { puts ##a }
end
def some_function
##a = 1
l.call
end
end
SomeClass.new.some_function
a similar way:
class Context
attr_reader :_previous, :_arguments
def initialize(_previous, _arguments)
#_previous = _previous
#_arguments = _arguments
end
end
def _code_def(_previous, _arguments = [], &_block)
define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
Context.new(_method_previous, _method_arguments).instance_eval(&_block)
end
end
_code_def('something') do
puts _previous
puts _arguments
end

How to overwrite defined? operator?

if you have something like:
module Real
A = 1
end
when you do defined?(Real::A) you get 'constant' which is a truish value. Now if i do something like:
module Virtual
def self.constants
[:A] + super
end
def self.const_missing(sym)
return 1 if sym == :A
super
end
def self.const_defined?(sym)
return true if sym == :A
super
end
end
defined?(Virtual::A) return nil. Is there some way to overwrite defined? behaviour to take metaprogrammed constants into acccount?
defined? is actually an operator (and not just syntactic sugar like +) and as such cannot be redefined. The proper solution would be to not use defined? for checking but aforementioned const_defined?. defined? isn't intended for meta-programming and works on the parser level, which is why it can give rather detailed information on the type of expression.

Trying to re-define += in an Array subclass doesn't seem to do anything?

In an Array subclass (just an array that does some coercing of input values) I've defined #concat to ensure values are coerced. Since nobody ever uses #concat and is more likely to use #+= I tried to alias #+= to #concat, but it never seems to get invoked. Any ideas?
Note that the coercing is actually always to objects of a particular superclass (which accepts input via the constructor), in case this code seems not to do what I describe. It's part of an internal, private API.
class CoercedArray < Array
def initialize(type)
super()
#type = type
end
def push(object)
object = #type.new(object) unless object.kind_of?(#type)
super
end
def <<(object)
push(object)
end
def concat(other)
raise ArgumentError, "Cannot append #{other.class} to #{self.class}<#{#type}>" unless other.kind_of?(Array)
super(other.inject(CoercedArray.new(#type)) { |ary, v| ary.push(v) })
end
alias :"+=" :concat
end
#concat is working correctly, but #+= seems to be completely by-passed.
Since a += b is syntactic sugar for a = a + b I'd try to overwrite the + method.

Resources