Feature #15240
openSet operations check for is_a?(Set), rather than allowing duck typing
Description
Hello there 👋
Ruby's Set
, unlike Array
or Hash
, cannot easily interoperate with user-created classes as several operations (#==
, #flatten
, #flatten!
, #intersect?
, #disjoint?
, #subset?
, #proper_subset?
, #superset?
, #proper_superset?
) check that the other class is_a?(Set)
, rather than allowing duck-typing.
Example:
require 'set'
class MySet
include Enumerable
def each(&block) [:my, :set].each(&block) end
def size() to_a.size end
end
puts Set[:set].subset?(MySet.new)
=> Traceback (most recent call last):
1: from testcase.rb:8:in `<main>'
set.rb:292:in `subset?': value must be a set (ArgumentError)
The only way I've found of going around this issue and looking at the Ruby sources, is to fake a response to is_a?
:
require 'set'
class MySet
include Enumerable
def each(&block) [:my, :set].each(&block) end
def size() to_a.size end
def is_a?(klass) super || klass == Set end # <== Hack! 😭
end
puts Set[:set].subset?(MySet.new)
=> true
This is a very ugly hack, and instead it would be nice if, instead, I could just provide a to_set
method that Set
could call to allow duck typing.
I'm willing to work on a patch to solve this (would be pretty nice to do my first contribution to Ruby core!), so hopefully we can discuss how this problem can be tackled.
Background / TL;DR¶
This issue came about as I am the creator of a gem called persistent-💎. This gem provides immutable arrays, hashes and sets. Most of the hard work is delegated to another gem (hamster), but I've added a number of tweaks to allow the persistent-💎 variants to easily interoperate with their Ruby counterparts.
Because I wanted to allow Persistent💎::Set
instances to be used together with Ruby's Set
, I studied the set.rb
implementation and came up with the is_a?(Set)
hack above. This works on all Ruby versions the gem supports (1.9->2.6), but broke on JRuby 9.2 when a new optimized Set
implementation was added, that did not do the is_a?(Set)
check and thus broke the hack.
I've brought up this issue with the JRuby developers -- https://github.com/jruby/jruby/issues/5227 -- and from there we moved the discussion to ruby/spec -- https://github.com/ruby/spec/pull/629.
We ended up concluding that it would make sense to raise this on the Ruby tracker as something that should be fixed on Set
itself, rather than codifying this hack as something that Ruby is expected to support.
Since Ruby sets already support an implicit conversion method -- to_set
-- it seems natural to replace the is_a?(Set)
with some kind of other.respond_to?(:to_set) && other = other.to_set
in all places where the is_a?(Set)
was being used. Note that his would be all that's needed to be able to use a Set
duck-type --- the Persistent💎::Set
specs are a pretty good proof of it.
Thanks for the time 🙏, and rock on 💪!
Files