Search code examples
rubytypessetenumerable

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


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!


Solution

  • 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.