Issue #7604 has been updated by boris_stitnicky (Boris Stitnicky).


(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.
----------------------------------------
Feature #7604: Make === comparison operator ability to delegate comparison to an argument
https://bugs.ruby-lang.org/issues/7604#change-35050

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/