From: "prijutme4ty (Ilya Vorontsov)" Date: 2012-12-26T11:33:11+09:00 Subject: [ruby-core:51139] [ruby-trunk - Feature #7604] Make === comparison operator ability to delegate comparison to an argument Issue #7604 has been updated by prijutme4ty (Ilya Vorontsov). boris_stitnicky (Boris Stitnicky) wrote: > (2) and (1) are two steps of the same campaign, to make the behavior you described possible, but (1) might be easier and mildly useful on its own. Current #coerce would solve the problem provided that you make it return special objects with customized multiple operator methods, similar to your Predicate. Why not make a coerce-based gem demonstrating this? I would be interested in using it personally. You would have to find and patch those scattered #=== methods, while I am more interested in :+, :-, :*, :/, :**, and :<=>. We could have common special object for all of these. I will create a proof-of-concept gem, but not sure that I'll be able to create a native extension. So arithmetical operations can become much slower. ---------------------------------------- Feature #7604: Make === comparison operator ability to delegate comparison to an argument https://bugs.ruby-lang.org/issues/7604#change-35079 Author: prijutme4ty (Ilya Vorontsov) Status: Open Priority: Normal Assignee: Category: Target version: =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 -- http://bugs.ruby-lang.org/