-
-
Save steveklabnik/6071687 to your computer and use it in GitHub Desktop.
irb(main):001:0> class MyArray < Array | |
irb(main):002:1> def foo | |
irb(main):003:2> "foo" | |
irb(main):004:2> end | |
irb(main):005:1> end | |
=> nil | |
irb(main):006:0> a1 = MyArray.new | |
=> [] | |
irb(main):007:0> a1.foo | |
=> "foo" | |
irb(main):008:0> (a1 + []).foo | |
NoMethodError: undefined method `foo' for []:Array | |
from (irb):8 | |
from /opt/rubies/ruby-2.0.0-p195/bin/irb:12:in `<main>' | |
irb(main):007:0> MyArray.new.to_a.foo | |
NoMethodError: undefined method `foo' for []:Array | |
from (irb):7 | |
from /opt/rubies/ruby-2.0.0-p195/bin/irb:12:in `<main>' |
Doesnt that have to do with how the + method returns an array object? not MyArray? Still i agree with being careful on C classes.
+1 to @agmcleod
(a1 + []) evaluates to an Array not a MyArray, why is that cheating?
class MyArray < Array
def foo() "foo" end
end
(MyArray.new << [1, 4, 5]).foo
=> "foo"
It also works just like you'd expect when you're mutating the existing MyArray instead of forcing a new Array to be created (as #+ does).
Array#concat instead of Array#+ works fine
class MyArray < Array
def foo() "foo" end
end
a1 = MyArray.new
a1.concat([1,2])
a1
=> [1,2]
a1.foo
=> "foo"
Override #+
to return a MyArray
. Overriding methods like this is surely not a rat hole.
Override ALL THE THINGS!
P.S. I would love to hear thoughts on inheriting from C classes vs opening them up and adding to them.
with MyArray.new.to_a.foo
isn't it obvious you want change it to Array from MyArray? It's almost the same as trying MyArray.new.to_s.foo
and expecting it to have foo method
@driv3r +1 👍
What about OStruct?
I tend to wrap hashes/arrays in a containing class then delegate; but when I'm lazy I inherit :X
How is this connected to core classes at all? Totally the same can happen with pure Ruby.
Like others said, unless you'd define your own MyArray#+
operator to coerce the result into a MyArray
instead of Array
, of course it's gonna ruin your day. This is behavior that is easily avoidable, and I don't think it's a good reason to advise everybody away from inheriting from core classes.
Wrote a longer post with more examples: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
This behavior is easily avoidable when implementing the core types, or any types, although it may have performance implications for naive or non-JITed implementations. Array#+ could instantiate self.class instead of hard-coding an Array. Here's a version of Array#+ that doesn't have this problem:
class Array
def +(other)
result = self.class.new
result.replace(self)
result.concat(other)
result
end
end
That makes Steve's examples work:
> class MyArray < Array; def foo; "foo"; end; end
=> nil
> (MyArray.new + []).foo
=> "foo"
It clearly is connected in the core classes, in that they were implemented without considering reuse in this way. Of course, this raises an awkward question: what if self
and other
are both subclasses of Array, but not the same subclass? Do you return the self type, or the other type? Returning the base type (Array) makes some level of sense in that case, but in practice this comes up much less than the simple "I want to subclass Array and get reasonable results" case.
There's actually not really a good reason here why this couldn't just work properly, just that they choose not to do it. Or go back and forth on certain methods in a random way.