Issue #15240 has been updated by knu (Akinori MUSHA).


I can't recall everything in details, but I think interaction with other types of objects, especially comparison operations, was out of the scope when I wrote Set, and there were some points to consider in my mind.

First, implicit conversion in comparison has not been adopted in Ruby unlike in PHP and JavaScript where programmers often have hard times with it, and that was why I took a conservative path.

e.g.
```ruby
o = Object.new
def o.to_int; 1; end
def o.to_ary; [1,2]; end
1 == o #=> false
[1,2] == o #=> false
```

We'd still have gone with it in Set, but it seemed to me that the apply to_set and compare strategy wouldn't work well with arrays for example, because that'd make `[1, 2, 2]` a subset of and equal to `Set[1, 2]`.

Comparison of both size and elements (as proposed above) could have worked, but in retrospect, there was something called String that was Enumerable but the size did not reflect the number of enumerated elements.  For those who don't know, in Ruby 1.8, String#size would return a byte size and #each would enumerate lines.

Those above are just my excuses; maybe it's time to think and move forward.

----------------------------------------
Bug #15240: Set operations check for is_a?(Set), rather than allowing duck typing
https://bugs.ruby-lang.org/issues/15240#change-75870

* Author: ivoanjo (Ivo Anjo)
* Status: Open
* Priority: Normal
* Assignee: knu (Akinori MUSHA)
* Target version: 
* ruby -v: ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
* Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN
----------------------------------------
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:

```ruby
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?`:

```ruby
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-](https://gitlab.com/ivoanjo/persistent-dmnd/). This gem provides immutable arrays, hashes and sets. Most of the hard work is delegated to another gem ([hamster](https://github.com/hamstergem/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](https://gitlab.com/ivoanjo/persistent-dmnd/blob/master/spec/unit/set_spec.rb) are a pretty good proof of it.

Thanks for the time , and rock on !



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>