From: knuckles@... Date: 2019-01-01T22:13:24+00:00 Subject: [ruby-core:90845] [Ruby trunk Bug#15240] Set operations check for is_a?(Set), rather than allowing duck typing Issue #15240 has been updated by ivoanjo (Ivo Anjo). Thanks for chiming in, @knu! Interestingly in we had discussed and were considering that the existing `#to_set` could be seen as an *implicit* conversion, but as you've pointed out, the current `Enumerable#to_set` monkey patch that loading `set.rb` adds definitely makes `#to_set` look a lot more like an *explicit* conversion -- as it does not make any sense for `Set[1, 2] == [1, 2, 2]`, and in general for any random `Enumerable` to suddenly be treated like `set`. Notice however that there's something missing in your `to_ary` example that makes it work: ``` o = Object.new def o.to_ary; [1, 2]; end def o.==(other); super || other == to_ary; end [1, 2] == o #=> true ``` What `Array` actually does is check for `respond_to?(:to_ary)` and in that case it calls the `==` on our custom object. Ideally a similar behavior would be added to `Set`. So perhaps if we were to consider `#to_set` to be the *explicit* conversion, we could introduce another one to be an implicit counterpart, like `#to_ary` or `#to_hash`. Unfortunately, I can't think of a great name for it, so as a **working title only** I'll refer to it as `#to_st`. What do you think of this updated proposal? ---------------------------------------- Bug #15240: Set operations check for is_a?(Set), rather than allowing duck typing https://bugs.ruby-lang.org/issues/15240#change-76033 * 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 `
' 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 -- -- and from there we moved the discussion to ruby/spec -- . 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: