Feature #7604
openMake === comparison operator ability to delegate comparison to an argument
Description
=begin
I propose to expand default behaviour of === operator in the following way:
Objects have additional instance method Object#reverse_comparison?(other) which is false by default in all basic classes.
Each class that overrides Object#===(other) should check whether reverse_comparison? is true or false
If it is false, behavior is not changed at all.
If it is true, comparison is delegated to === method of an argument with self as an argument.
This technique can help in constructing RSpec-style matchers for case statement. Example:
usual method call¶
arr = %w[cat dog rat bat]
puts arr.end_with?(%w[dog bat]) # ==> false
puts arr.end_with?(%w[rat bat]) # ==> true
puts arr.end_with?(%w[bat]) # ==> true
predicate-style case¶
case %w[cat dog rat bat].end_with?
when %w[dog bat]
puts '..., dog, bat'
when %w[rat bat]
puts '..., rat, bat'
when %w[bat]
puts '..., bat'
else
puts 'smth else'
end
==> ..., rat, bat¶
Code needed to run this is not very complex:
class Object
def reverse_comparison?(other)
false
end
alias_method :'old===', :'==='
def ===(other)
(other.reverse_comparison?(self) ? (other.send 'old===',self) : (self.send 'old===',other))
end
end
class Predicate
def initialize(&block)
@block = block
end
def reverse_comparison?(other)
true
end
def ===(*args)
@block.call(*args)
end
end
class Array
alias_method :'old===', :'==='
def ===(other)
other.reverse_comparison?(self) ? (other.send('===',self)) : (self.send('old===',other))
end
def end_with?(expected_elements = nil)
return last(expected_elements.size) == expected_elements if expected_elements
Predicate.new{|suffix| last(suffix.size) == suffix }
end
end
This technique looks powerful and beautiful for me. One detail is that obj#reverse_comparison? can distinguish different types of arguments and returns true only for certain types of given object. Also this can be used to prevent double-mirroring (as shown below)
The problem is that many base classes already defined custom === operator, so each of those classes (Fixnum, Float, String, Regexp, Range etc) should be redefined in such a way to make a solution full-fledged.
Another problem is case that both objects defined reverse_comparison? to return true. In my solution Predicate#=== just ignores result of revese_comparison? which is not consistent.
Another possible way is to raise errors on double mirroring:
def reverse_comparison?(other)
raise 'double mirroring' if @__mirroring_started
@__mirroring_started = true
return true unless other.reverse_comparison?(self)
false
ensure
remove_instance_variable :@__mirroring_started
end
My proposal is to add reverse_comparison? method and change base classes operator === to use its result as shown above. May be it's worth also to make a class analogous to Predicate in stdlib.
=end