Uniform vector class, inheriting from Array: How to make sure thatmethods return a Vector and not an

Discussion in 'Ruby' started by Thomas, May 22, 2005.

  1. Thomas

    Thomas Guest

    Hi folks,

    I recently tried to implement a uniform vector class being defined as a
    vector the elements of which all comply to some kind of common interface
    or are a subclass of some prototype class.

    My first naive approach was to inherit from Array and to overwrite some
    methods to make sure that new elements are ok. This works fine for
    methods like []=, <<, or unshift but when implementing + I ran into the
    following problem: Array#+ returns an Array and not a Vector which is
    why I have to create a new Vector from the result of Array#+. As Array#+
    already creates a new Array, this probably isn't very efficient and I
    don't like the idea of having to do this for all possible methods that
    return an Array. I was hoping to be able to somehow limit my
    modifications of Array to a few essential methods.

    So, the question is: Does somebody know a way to make sure that methods
    returning an Array (+, &, -, * etc.) always return a Vector without
    having to redefine each of them?

    Please find my current toy implementation down below.

    Cheers,
    Thomas.


    class Vector < Array
    def []=(pos, val)
    check_values([val])
    super
    end

    def +(values)
    check_values(values)
    Vector.new(super)
    end

    def <<(*values)
    check_values(values)
    super
    end

    def unshift(*values)
    check_values(values)
    super
    end

    def check_values(values)
    unless defined?(@protoclass)
    prototype = self[0] || values[0]
    @protoclass = prototype.class
    end
    values.each do |e|
    unless e.kind_of?(@protoclass)
    raise TypeError, "Expected #{@protoclass} but got
    #{e.class}", caller[2..-1]
    end
    end
    end
    end

    if __FILE__ == $0
    v = Vector.new
    # these are okay
    v << 1
    v[2] = 2
    p v + [1,2,3]
    v += [1,2,3]
    v.unshift(3)

    # but this should throw an error (that's what this is all about)
    v << "a"
    end
    Thomas, May 22, 2005
    #1
    1. Advertising

  2. Re: Uniform vector class, inheriting from Array: How to make sure that methods return a Vector and not an Array?

    On 22/05/05, Thomas <> wrote:
    > Hi folks,
    >=20
    > I recently tried to implement a uniform vector class being defined as a
    > vector the elements of which all comply to some kind of common interface
    > or are a subclass of some prototype class.
    >=20
    > My first naive approach was to inherit from Array and to overwrite some
    > methods to make sure that new elements are ok. This works fine for
    > methods like []=3D, <<, or unshift but when implementing + I ran into the
    > following problem: Array#+ returns an Array and not a Vector which is
    > why I have to create a new Vector from the result of Array#+. As Array#+
    > already creates a new Array, this probably isn't very efficient and I
    > don't like the idea of having to do this for all possible methods that
    > return an Array. I was hoping to be able to somehow limit my
    > modifications of Array to a few essential methods.
    >=20
    > So, the question is: Does somebody know a way to make sure that methods
    > returning an Array (+, &, -, * etc.) always return a Vector without
    > having to redefine each of them?
    >=20
    > Please find my current toy implementation down below.
    >=20


    Hello Thomas,

    I'm quite shure you don't need to do what you are doing, because this
    violates duck typing, but if you really want to you can use something
    like the below.

    Here I don't inherit from an array, but forward to an array, which
    often is the simpler approach.

    Please rethink your design. If you really want to do this for speed,
    you could implement a simple C extension that includes a numeric
    array, or use the narray class. Otherwise just don't put anything
    wrong into the array, and everything will work out fine. And if
    someone wants to put something into the array that quacks like a duck
    but is no duck, also everything will continue to work.

    best regards,

    Brian

    class Vector
    def initialize(*args)
    @numbers =3D args
    check_values(args)
    end

    def method_missing(method, *args, &block)
    result =3D @numbers.send(method, *args, &block)
    if result.is_a?Array
    Vector.new(*result) =20
    else
    result
    end
    end

    private
    def check_values(values)
    return true if @numbers.empty? and values.empty?
    unless defined?(@protoclass)
    prototype =3D @numbers[0] || values[0]
    @protoclass =3D prototype.class
    end
    values.each do |e|
    unless e.kind_of?(@protoclass)
    =09raise TypeError, "Expected #{@protoclass} but got #{e.class}", caller[2.=
    -1]
    end
    end
    end
    end

    if __FILE__ =3D=3D $0
    v =3D Vector.new
    # these are okay
    p v
    v << 1
    p v
    v[1] =3D 2
    p v
    p (v + [1,2,3])
    v +=3D [1,2,3]
    p v
    v.unshift(3)
    p v

    # but this should throw an error (that's what this is all about)
    v << "a"
    p v
    end


    --=20
    http://ruby.brian-schroeder.de/

    Stringed instrument chords: http://chordlist.brian-schroeder.de/
    Brian Schröder, May 22, 2005
    #2
    1. Advertising

  3. Re: Uniform vector class, inheriting from Array: How to make surethat methods return a Vector and not an Array?

    Brian Schröder ha scritto:
    <snip>
    >
    > I'm quite shure you don't need to do what you are doing, because this
    > violates duck typing, but if you really want to you can use something
    > like the below.
    >
    > Here I don't inherit from an array, but forward to an array, which
    > often is the simpler approach.



    this makes me think: the delegation approach is usually simpler.
    But is this because of an underlying fault of the inheritance mechanic
    or is it the obvious more simple thing?
    I've often thought that there must be a better way of handling this
    "generative" methods like "+" (but, say, a polymorphic #map could be the
    same) which make subclassing harder, has someone haver thought the same?
    Is there something in other languages that allow to overcome this kind
    of limitations?
    gabriele renzi, May 22, 2005
    #3
  4. Thomas

    Thomas Guest

    Re: Uniform vector class, inheriting from Array: How to make surethat methods return a Vector and not an Array?

    > I'm quite shure you don't need to do what you are doing, because this
    > violates duck typing


    Thank you very much for pointing out how to use method_missing here.

    BTW I don't think that it "violates" duck typing. The idea is to have a
    collection type class with "entrance rules" that can guarantee that
    every single element complies with some sort of standard/interface/rule
    set/constraints/contracts ... Using kind_of? is only one possible
    constraint here. Another one could be respond_to?:)quack). On the long
    run, I would like it to look/feel like this:

    class QuackQuack < UniformArray
    add_contstraint lambda {|e| e.respond_to?:)quack)}
    end

    class ArmyOfDucks < UniformArray
    add_contstraint lambda {|e| e.kind_of?(Duck)}
    end

    class FlockOfDucks < UniformArray
    add_contstraint lambda {|e| e.kind_of?(Duck)}
    permit nil # There may be nil values
    allow :nested # The array may be nested
    end

    d = UniformArray.new
    d.add_contstraint lambda {|e| e.kind_of?(Numeric) and e >= 0 and e <= 10}
    d.add_handler lambda {|e| e > 10 ? 10 : 0}

    etc.

    I don't think this is incompatible with duck typing. It's just some sort
    of specialized collection type of class.

    Cheers,
    Thomas.
    Thomas, May 23, 2005
    #4
  5. Thomas

    Mark Hubbart Guest

    Re: Uniform vector class, inheriting from Array: How to make sure that methods return a Vector and not an Array?

    On 5/23/05, Thomas <> wrote:
    > > I'm quite shure you don't need to do what you are doing, because this
    > > violates duck typing

    >=20
    > Thank you very much for pointing out how to use method_missing here.
    >=20
    > BTW I don't think that it "violates" duck typing. The idea is to have a
    > collection type class with "entrance rules" that can guarantee that
    > every single element complies with some sort of standard/interface/rule
    > set/constraints/contracts ... Using kind_of? is only one possible
    > constraint here. Another one could be respond_to?:)quack). On the long
    > run, I would like it to look/feel like this:
    >=20
    > class QuackQuack < UniformArray
    > add_contstraint lambda {|e| e.respond_to?:)quack)}
    > end
    >=20
    > class ArmyOfDucks < UniformArray
    > add_contstraint lambda {|e| e.kind_of?(Duck)}
    > end
    >=20
    > class FlockOfDucks < UniformArray
    > add_contstraint lambda {|e| e.kind_of?(Duck)}
    > permit nil # There may be nil values
    > allow :nested # The array may be nested
    > end
    >=20
    > d =3D UniformArray.new
    > d.add_contstraint lambda {|e| e.kind_of?(Numeric) and e >=3D 0 and e <=3D=

    10}
    > d.add_handler lambda {|e| e > 10 ? 10 : 0}
    >=20
    > etc.
    >=20
    > I don't think this is incompatible with duck typing. It's just some sort
    > of specialized collection type of class.


    Use of a "uniform" array would seem to preclude duck-typing. Even if
    you use respond_to? instead of kind_of?, it wouldn't catch objects
    that use method_missing to handle messages. For example:

    class Wrapper
    def initialize(obj)
    @obj =3D obj
    end
    def method_missing(*args, &block)
    @obj.__send__ *args, &block
    end
    end

    If you use this class to wrap an object, code that relies entirely on
    duck typing will still be able to use the object, even though it won't
    respond_to? much of anything, and it won't be kind_of? the class you
    want.

    On the other hand, if you call it ConstrainedArray and focus on
    constraints rather than types, it could end up being quite useful...

    cheers,
    Mark
    Mark Hubbart, May 23, 2005
    #5
  6. Re: Uniform vector class, inheriting from Array: How to make sure that methods return a Vector and not an Array?

    On 5/22/05, gabriele renzi <> wrote:
    > this makes me think: the delegation approach is usually simpler.
    > But is this because of an underlying fault of the inheritance mechanic
    > or is it the obvious more simple thing?


    I think you've got it. Classes which have methods which return
    instances of their own class should, once extended, return instances
    of that extended class.

    I'm not sure how it would work, something like return self.new.

    Douglas
    Douglas Livingstone, May 23, 2005
    #6
  7. Thomas

    Jacob Fugal Guest

    Re: Uniform vector class, inheriting from Array: How to make sure that methods return a Vector and not an Array?

    On 5/23/05, Mark Hubbart <> wrote:
    > Use of a "uniform" array would seem to preclude duck-typing. Even if
    > you use respond_to? instead of kind_of?, it wouldn't catch objects
    > that use method_missing to handle messages. For example:
    >=20
    > class Wrapper
    > def initialize(obj)
    > @obj =3D obj
    > end
    > def method_missing(*args, &block)
    > @obj.__send__ *args, &block
    > end
    > end
    >=20
    > If you use this class to wrap an object, code that relies entirely on
    > duck typing will still be able to use the object, even though it won't
    > respond_to? much of anything, and it won't be kind_of? the class you
    > want.


    Well, on this point, isn't it recommended that whenever you override
    method_missing you should also override respond_to? as well so that
    they agree? I thought this was in the docs but can't find it now in
    either ri method_missing or ri respond_to. Despite that, I know I've
    heard it mentioned various times on this list and in general it seems
    like a Good Idea.

    Jacob Fugal
    Jacob Fugal, May 23, 2005
    #7
  8. Thomas

    Mark Hubbart Guest

    Re: Uniform vector class, inheriting from Array: How to make sure that methods return a Vector and not an Array?

    On 5/23/05, Jacob Fugal <> wrote:
    > On 5/23/05, Mark Hubbart <> wrote:
    > > Use of a "uniform" array would seem to preclude duck-typing. Even if
    > > you use respond_to? instead of kind_of?, it wouldn't catch objects
    > > that use method_missing to handle messages. For example:
    > >
    > > class Wrapper
    > > def initialize(obj)
    > > @obj =3D obj
    > > end
    > > def method_missing(*args, &block)
    > > @obj.__send__ *args, &block
    > > end
    > > end
    > >
    > > If you use this class to wrap an object, code that relies entirely on
    > > duck typing will still be able to use the object, even though it won't
    > > respond_to? much of anything, and it won't be kind_of? the class you
    > > want.

    >=20
    > Well, on this point, isn't it recommended that whenever you override
    > method_missing you should also override respond_to? as well so that
    > they agree? I thought this was in the docs but can't find it now in
    > either ri method_missing or ri respond_to. Despite that, I know I've
    > heard it mentioned various times on this list and in general it seems
    > like a Good Idea.


    I'd never heard it mentioned on the list (that I remember), but it
    seems to be mentioned in the style guide, on the rubygarden wiki. No
    disrespect to those who posted it, but what you see on a wiki doesn't
    always reflect the views of the entire community. (not that I'm the
    authority on what this community thinks)

    Grepping the standard library, I found 24 instances of method_missing
    being overridden, and 0 instances of respond_to? being overridden:

    mark@eMac% ruby -e'p ARGF.grep(/def (\S+\.)?method_missing/).size' **/*.rb
    24
    mark@eMac% ruby -e'p ARGF.grep(/def (\S+\.)?respond_to\?/).size' **/*.rb
    0

    So, even if the programmer follows that rule, the stdlib doesn't seem
    to, so you can still get objects creeping in that will respond to a
    method without raising an error, but don't respond_to? it.

    cheers,
    Mark
    Mark Hubbart, May 23, 2005
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page