Here's some example code:
class Obj
attr :c, true
def == that
p '=='
that.c == self.c
end
def <=> that
p '<=>'
that.c <=> self.c
end
def equal? that
p 'equal?'
that.c.equal? self.c
end
def eql? that
p 'eql?'
that.c.eql? self.c
end
end
a = Obj.new
b = Obj.new
a.c = 1
b.c = 1
p [a] | [b]
It prints 2 objects but it should print 1 object. None of the comparison methods get called. How is Array.| comparing for equality?
Array#| is implemented using hashs. So in order for your type to work well with it (as well as with hashmaps and hashsets), you'll have to implement eql? (which you did) and hash (which you did not). The most straight forward way to define hash meaningfully would be to just return c.hash.
Ruby's Array class is implemented in C, and from what I can tell, uses a custom hash table to check for equality when comparing objects in |. If you wanted to modify this behavior, you'd have to write your own version that uses an equality check of your choice.
To see the full implementation of Ruby's Array#|: click here and search for "rb_ary_or(VALUE ary1, VALUE ary2)"
Ruby is calling the hash functions and they are returning different values, because they are still just returning the default object_id. You will need to def hash and return something reflecting your idea of what makes an Obj significant.
>> class Obj2 < Obj
>> def hash; t = super; p ['hash: ', t]; t; end
>> end
=> nil
>> x, y, x.c, y.c = Obj2.new, Obj2.new, 1, 1
=> [#<Obj2:0x100302568 #c=1>, #<Obj2:0x100302540 #c=1>, 1, 1]
>> p [x] | [y]
["hash: ", 2149061300]
["hash: ", 2149061280]
["hash: ", 2149061300]
["hash: ", 2149061280]
[#<Obj2:0x100302568 #c=1>, #<Obj2:0x100302540 #c=1>]
Related
I'm interested in implementing a custom equality method for use in an array of objects in Ruby. Here's a stripped-back example:
class Foo
attr_accessor :a, :b
def initialize(a, b)
#a = a
#b = b
end
def ==(other)
puts 'doing comparison'
#a == #a && #b == #b
end
def to_s
"#{#a}: #{#b}"
end
end
a = [
Foo.new(1, 1),
Foo.new(1, 2),
Foo.new(2, 1),
Foo.new(2, 2),
Foo.new(2, 2)
]
a.uniq
I expected the uniq method to call Foo#==, and remove the last instance of Foo. Instead, I don't see the 'doing comparison' debug line and the array remains the same length.
Notes:
I'm using ruby 2.2.2
I've tried defining the method as ===
I have done it long-hand with a.uniq{|x| [x.a, x.b]}, but I don't like this solution it's making the code look pretty cluttered.
It compares values using their hash and eql? methods for efficiency.
https://ruby-doc.org/core-2.5.0/Array.html#method-i-uniq-3F
So you should override eql? (that is ==) and hash
UPDATE:
I cannot explain fully why is that, but overriding hash and == doesn't work. I guess it's cause by the way uniq is implemented in C:
From: array.c (C Method):
Owner: Array
Visibility: public
Number of lines: 20
static VALUE
rb_ary_uniq(VALUE ary)
{
VALUE hash, uniq;
if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
if (rb_block_given_p()) {
hash = ary_make_hash_by(ary);
uniq = rb_hash_values(hash);
}
else {
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
}
RBASIC_SET_CLASS(uniq, rb_obj_class(ary));
ary_recycle_hash(hash);
return uniq;
}
You can bypass that by using a block version of uniq:
> [Foo.new(1,2), Foo.new(1,2), Foo.new(2,3)].uniq{|f| [f.a, f.b]}
=> [#<Foo:0x0000562e48937cc8 #a=1, #b=2>, #<Foo:0x0000562e48937c78 #a=2, #b=3>]
Or use Struct instead:
F = Struct.new(:a, :b)
[F.new(1,2), F.new(1,2), F.new(2,3)].uniq
# => [#<struct F a=1, b=2>, #<struct F a=2, b=3>]
UPDATE2:
Actually in terms of overriding it's not the same if you override == or eql?. When I overriden eql? It worked as intended:
class Foo
attr_accessor :a, :b
def initialize(a, b)
#a = a
#b = b
end
def eql?(other)
(#a == other.a && #b == other.b)
end
def hash
[a, b].hash
end
def to_s
"#{#a}: #{#b}"
end
end
a = [
Foo.new(1, 1),
Foo.new(1, 2),
Foo.new(2, 1),
Foo.new(2, 2),
Foo.new(2, 2)
]
a.uniq
#=> [#<Foo:0x0000562e483bff70 #a=1, #b=1>,
#<Foo:0x0000562e483bff48 #a=1, #b=2>,
#<Foo:0x0000562e483bff20 #a=2, #b=1>,
#<Foo:0x0000562e483bfef8 #a=2, #b=2>]
You can find the answer in the documentation of Array#uniq (for some reason, it is not mentioned in the documentation of Enumerable#uniq):
It compares values using their hash and eql? methods for efficiency.
The contracts of hash and eql? are as follows:
hash returns an Integer that must be the same for objects which are considered equal, but does not necessarily have to be different for objects that are not equal. This means that different hashes mean that the objects are definitely not equal, but the same hash doesn't tell you anything. Ideally, hash should also be resistant to accidental and deliberate collisions.
eql? is value equality, usually stricter than == but less strict than equal? which is more or less identity: equal? should only return true if you compare an object to itself.
uniq? uses the same trick that is used in hash tables, hash sets, etc. to speed up lookups:
Compare the hashes. Computing a hash should normally be fast.
If the hashes are identical, then, and only then double-check using eql?.
So I need to create an instance method for Array that takes two arguments, the size of an array and an optional object that will be appended to an array.
If the the size argument is less than or equal to the Array.length or the size argument is equal to 0, then just return the array. If the optional argument is left blank, then it inputs nil.
Example output:
array = [1,2,3]
array.class_meth(0) => [1,2,3]
array.class_meth(2) => [1,2,3]
array.class_meth(5) => [1,2,3,nil,nil]
array.class_meth(5, "string") => [1,2,3,"string","string"]
Here is my code that I've been working on:
class Array
def class_meth(a ,b=nil)
self_copy = self
diff = a - self_copy.length
if diff <= 0
self_copy
elsif diff > 0
a.times {self_copy.push b}
end
self_copy
end
def class_meth!(a ,b=nil)
# self_copy = self
diff = a - self.length
if diff <= 0
self
elsif diff > 0
a.times {self.push b}
end
self
end
end
I've been able to create the destructive method, class_meth!, but can't seem to figure out a way to make it non-destructive.
Here's (IMHO) a cleaner solution:
class Array
def class_meth(a, b = nil)
clone.fill(b, size, a - size)
end
def class_meth!(a, b = nil)
fill(b, size, a - size)
end
end
I think it should meet all your needs. To avoid code duplication, you can make either method call the other one (but not both simulaneously, of course):
def class_meth(a, b = nil)
clone.class_meth!(a, b)
end
or:
def class_meth!(a, b = nil)
replace(class_meth(a, b))
end
As you problem has been diagnosed, I will just offer a suggestion for how you might do it. I assume you want to pass two and optionally three, not one and optionally two, parameters to the method.
Code
class Array
def self.class_meth(n, arr, str=nil)
arr + (str ? ([str] : [nil]) * [n-arr.size,0].max)
end
end
Examples
Array.class_meth(0, [1,2,3])
#=> [1,2,3]
Array.class_meth(2, [1,2,3])
#=> [1,2,3]
Array.class_meth(5, [1,2,3])
#=> [1,2,3,nil,nil]
Array.class_meth(5, [1,2,3], "string")
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"], "string")
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
#=> ["dog", "cat", "pig", nil, nil]
Array.class_meth(5, ["dog","cat","pig"], "string")
#=> ["dog", "cat", "pig", "string", "string"]
Before withdrawing his answer, #PatriceGahide suggested using Array#fill. That would be an improvement here; i.e., replace the operative line with:
arr.fill(str ? str : nil, arr.size, [n-arr.size,0].max)
self_copy = self does not make a new object - assignment in Ruby never "copies" or creates a new object implicitly.
Thus the non-destructive case works on the same object (the instance the method was invoked upon) as in the destructive case, with a different variable bound to the same object - that is self.equal? self_copy is true.
The simplest solution is to merely use #clone, keeping in mind it is a shallow clone operation:
def class_meth(a ,b=nil)
self_copy = self.clone # NOW we have a new object ..
# .. so we can modify the duplicate object (self_copy)
# down here without affecting the original (self) object.
end
If #clone cannot be used other solutions involve create a new array or obtain an array #slice (returns a new array) or even append (returning a new array) with #+; however, unlike #clone, these generally lock-into returning an Array and not any sub-type as may be derived.
After the above change is made it should also be apparent that it can written as so:
def class_meth(a ,b=nil)
clone.class_meth!(a, b) # create a NEW object; modify it; return it
# (assumes class_meth! returns the object)
end
A more appropriate implementation of #class_meth!, or #class_meth using one of the other forms to avoid modification of the current instance, is left as an exercise.
FWIW: Those are instance methods, which is appropriate, and not "class meth[ods]"; don't be confused by the ill-naming.
I have class Foo, and I overload two of its methods == and eql?:
class Foo
def initialize(bar)
#bar = bar
end
def bar
#bar
end
def ==(o)
self.bar == o.bar
end
def .eql?(o)
return ==(o)
end
end
I test that f1 and f2 below are equal with respect to the two methods:
u = User.find(12345)
f1 = Foo.new(u)
f2 = Foo.new(u)
f1 == f2 # => true
f1.eql?(f2) # => true
But Hash#has_key? does not render them equal:
{f1 => true}.has_key?(f2) # => false
What is the equality method used in Hash#has_key??
Most implementations of a hash type, Ruby’s included, rely on a hash first (for speed!) and then equality checks. To verify that it works, first, you can just add
def hash
1
end
After that, you should work on providing as many possible distinct return values for hash that will still be equal if the objects are considered equal (as long as it’s fast, of course).
It uses hash method. You may concatinate properties of your objects there or something like that. In your case you want hash value to be the same for 2 objects if and only if they are equal.
how can a write a class in ruby that has a procedures that i can call like this:
a = MyObj.new()
b = MyObj.new()
c = a * b
d = a / b
e = a - b
this is nicer than:
c = a.multiply(b)
...
thanks
class Foo
attr_accessor :value
def initialize( v )
self.value = v
end
def *(other)
self.class.new(value*other.value)
end
end
a = Foo.new(6)
#=> #<Foo:0x29c9920 #value=6>
b = Foo.new(7)
#=> #<Foo:0x29c9900 #value=7>
c = a*b
#=> #<Foo:0x29c98e0 #value=42>
You can find the list of operators that may be defined as methods here:
http://phrogz.net/ProgrammingRuby/language.html#operatorexpressions
You already got an answer on how to define the binary operators, so just as little addendum here's how you can define the unary - (like for negative numbers).
> class String
.. def -#
.. self.swapcase
.. end
.. end #=> nil
>> -"foo" #=> "FOO"
>> -"FOO" #=> "foo"
Just create methods whose name is the operator you want to overload, for example:
class MyObj
def / rhs
# do something and return the result
end
def * rhs
# do something and return the result
end
end
In Ruby, the * operator (and other such operators) are really just calling a method with the same name as the operator. So to override *, you could do something like this:
class MyObj
def *(obj)
# Do some multiplication stuff
true # Return whatever you want
end
end
You can use a similar technique for other operators, like / or +. (Note that you can't create your own operators in Ruby, though.)
Is there a good way to chain methods conditionally in Ruby?
What I want to do functionally is
if a && b && c
my_object.some_method_because_of_a.some_method_because_of_b.some_method_because_of_c
elsif a && b && !c
my_object.some_method_because_of_a.some_method_because_of_b
elsif a && !b && c
my_object.some_method_because_of_a.some_method_because_of_c
etc...
So depending on a number of conditions I want to work out what methods to call in the method chain.
So far my best attempt to do this in a "good way" is to conditionally build the string of methods, and use eval, but surely there is a better, more ruby, way?
You could put your methods into an array and then execute everything in this array
l= []
l << :method_a if a
l << :method_b if b
l << :method_c if c
l.inject(object) { |obj, method| obj.send(method) }
Object#send executes the method with the given name. Enumerable#inject iterates over the array, while giving the block the last returned value and the current array item.
If you want your method to take arguments you could also do it this way
l= []
l << [:method_a, arg_a1, arg_a2] if a
l << [:method_b, arg_b1] if b
l << [:method_c, arg_c1, arg_c2, arg_c3] if c
l.inject(object) { |obj, method_and_args| obj.send(*method_and_args) }
You can use tap:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
Sample class to demonstrate chaining methods that return a copied instance without modifying the caller.
This might be a lib required by your app.
class Foo
attr_accessor :field
def initialize
#field=[]
end
def dup
# Note: objects in #field aren't dup'ed!
super.tap{|e| e.field=e.field.dup }
end
def a
dup.tap{|e| e.field << :a }
end
def b
dup.tap{|e| e.field << :b }
end
def c
dup.tap{|e| e.field << :c }
end
end
monkeypatch: this is what you want to add to your app to enable conditional chaining
class Object
# passes self to block and returns result of block.
# More cumbersome to call than #chain_if, but useful if you want to put
# complex conditions in the block, or call a different method when your cond is false.
def chain_block(&block)
yield self
end
# passes self to block
# bool:
# if false, returns caller without executing block.
# if true, return result of block.
# Useful if your condition is simple, and you want to merely pass along the previous caller in the chain if false.
def chain_if(bool, &block)
bool ? yield(self) : self
end
end
Sample usage
# sample usage: chain_block
>> cond_a, cond_b, cond_c = true, false, true
>> f.chain_block{|e| cond_a ? e.a : e }.chain_block{|e| cond_b ? e.b : e }.chain_block{|e| cond_c ? e.c : e }
=> #<Foo:0x007fe71027ab60 #field=[:a, :c]>
# sample usage: chain_if
>> cond_a, cond_b, cond_c = false, true, false
>> f.chain_if(cond_a, &:a).chain_if(cond_b, &:b).chain_if(cond_c, &:c)
=> #<Foo:0x007fe7106a7e90 #field=[:b]>
# The chain_if call can also allow args
>> obj.chain_if(cond) {|e| e.argified_method(args) }
Although the inject method is perfectly valid, that kind of Enumerable use does confuse people and suffers from the limitation of not being able to pass arbitrary parameters.
A pattern like this may be better for this application:
object = my_object
if (a)
object = object.method_a(:arg_a)
end
if (b)
object = object.method_b
end
if (c)
object = object.method_c('arg_c1', 'arg_c2')
end
I've found this to be useful when using named scopes. For instance:
scope = Person
if (params[:filter_by_age])
scope = scope.in_age_group(params[:filter_by_age])
end
if (params[:country])
scope = scope.in_country(params[:country])
end
# Usually a will_paginate-type call is made here, too
#people = scope.all
Use #yield_self or, since Ruby 2.6, #then!
my_object.
then{ |o| a ? o.some_method_because_of_a : o }.
then{ |o| b ? o.some_method_because_of_b : o }.
then{ |o| c ? o.some_method_because_of_c : o }
Here's a more functional programming way.
Use break in order to get tap() to return the result. (tap is in only in rails as is mentioned in the other answer)
'hey'.tap{ |x| x + " what's" if true }
.tap{ |x| x + "noooooo" if false }
.tap{ |x| x + ' up' if true }
# => "hey"
'hey'.tap{ |x| break x + " what's" if true }
.tap{ |x| break x + "noooooo" if false }
.tap{ |x| break x + ' up' if true }
# => "hey what's up"
Maybe your situation is more complicated than this, but why not:
my_object.method_a if a
my_object.method_b if b
my_object.method_c if c
I use this pattern:
class A
def some_method_because_of_a
...
return self
end
def some_method_because_of_b
...
return self
end
end
a = A.new
a.some_method_because_of_a().some_method_because_of_b()
If you're using Rails, you can use #try. Instead of
foo ? (foo.bar ? foo.bar.baz : nil) : nil
write:
foo.try(:bar).try(:baz)
or, with arguments:
foo.try(:bar, arg: 3).try(:baz)
Not defined in vanilla ruby, but it isn't a lot of code.
What I wouldn't give for CoffeeScript's ?. operator.
I ended up writing the following:
class Object
# A naïve Either implementation.
# Allows for chainable conditions.
# (a -> Bool), Symbol, Symbol, ...Any -> Any
def either(pred, left, right, *args)
cond = case pred
when Symbol
self.send(pred)
when Proc
pred.call
else
pred
end
if cond
self.send right, *args
else
self.send left
end
end
# The up-coming identity method...
def itself
self
end
end
a = []
# => []
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(true, :itself, :push, 2)
# => [1, 2]