Skip to content

Instantly share code, notes, and snippets.

@steveklabnik
Last active December 20, 2015 04:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steveklabnik/6071687 to your computer and use it in GitHub Desktop.
Save steveklabnik/6071687 to your computer and use it in GitHub Desktop.
Don't inherit from core classes! They will ruin your day! They're implemented in C for speed, and cheat.
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>'
@dbussink
Copy link

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.

@agmcleod
Copy link

Doesnt that have to do with how the + method returns an array object? not MyArray? Still i agree with being careful on C classes.

@pastafari
Copy link

+1 to @agmcleod

(a1 + []) evaluates to an Array not a MyArray, why is that cheating?

@dhh
Copy link

dhh commented Jul 24, 2013

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

@whitequark
Copy link

@dbussink @agmcleod Returning MyArray only from MyArray#+ would break symmetry of +. Do you suggest that Array, and potentially every class ever which implements symmetric operators, should use a Numeric-like coercion facility?

@PriteshJain
Copy link

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"

@andrewpthorp
Copy link

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.

@driv3r
Copy link

driv3r commented Jul 24, 2013

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

@orlando
Copy link

orlando commented Jul 24, 2013

@driv3r +1 👍

@zspencer
Copy link

What about OStruct?

I tend to wrap hashes/arrays in a containing class then delegate; but when I'm lazy I inherit :X

@inossidabile
Copy link

How is this connected to core classes at all? Totally the same can happen with pure Ruby.

@davidcelis
Copy link

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.

@steveklabnik
Copy link
Author

@garybernhardt
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment