Project

General

Profile

Actions

Feature #15240

open

Set operations check for is_a?(Set), rather than allowing duck typing

Added by ivoanjo (Ivo Anjo) over 3 years ago. Updated over 2 years ago.

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:89500]

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

set-duck-typing-15240.patch (6.85 KB) set-duck-typing-15240.patch jeremyevans0 (Jeremy Evans), 08/27/2019 08:10 PM
Actions

Also available in: Atom PDF