Ruby: How to allow only a certain kind of objects to be added to a set

538 views Asked by At

I'd like to create a Set that only allows a certain kind of (kind_of?) objects to be added and an exception to be raised on the attempt to add an alien object.

I haven't found any resources yet and before I start messing around with the ruby core classes I'd be grateful for any advice how to achieve this in an "unobtrusive" manner.

Addition: My goal actually is to create a "templated" container class much like in C++ (e.g. Set) so that I can set the type once at definition of an instance and compare two sets on a class level if they are the same (i.e accept the same type) but keeping interoperability with "default" Sets, as an example set_instance_a.class == set_instance_b.class should yield true if they accept the same type of object.

An idea I had was to overload the ::[] operator so that I could write something like my_set = MySet[Foo] which should return a MySet instance that only accepts objects of type Foo

Thanks!

2

There are 2 answers

0
mu is too short On BEST ANSWER

Class is an object too so you can create new classes at runtime as needed. The Class constructor allows you to specify the base class and even evaluate code within the context of the new class:

new(super_class=Object) → a_class
new(super_class=Object) { |mod| ... } → a_class

Creates a new anonymous (unnamed) class with the given superclass (or Object if no parameter is given). You can give a class a name by assigning the class object to a constant.

If a block is given, it is passed the class object, and the block is evaluated in the context of this class using class_eval.

class_eval lets you define methods and aliases. All this means that you can say things like this:

module Kernel
  def MySet(c)
    Class.new(Set) do
      @@allowed = c
      def add(o)
        raise ArgumentError.new("#{o.class} is stinky!") if(!o.is_a?(@@allowed))
        super(o)
      end
      alias_method :<<, :add
    end
  end
end

And then:

s = MySet(String).new
s.add(6)        # Exception!
s << 'pancakes' # Allowed

I patched the MySet method into Kernel to be consistent with the Array, Complex, ... methods-that-pretend-to-be-functions, you can put it anywhere you'd like.

2
sawa On
require "set"

class MySet < Set
  def add e
    raise "Invalid element #{e}" unless e.kind_of?(Foo)
    super(e)
  end
end

or, if you do not want to use a subclass, then:

require "set"

class Set
  alias original_add :add
  def add e
    raise "Invalid element #{e}" unless e.kind_of?(Foo)
    original_add(e)
  end
end

Edit added in 2017 To do it without subclassing in a modern way,

require "set"

module MySet
  def add e
    raise "Invalid element #{e}" unless e.kind_of?(Foo)
    super(e)
  end
end

class Set
  prepend MySet
end