A very short and simple question: is there a function to "unextend" an
object ? Obviously it does not exist under that name, nor could I find
anything like it. It would be useful though to do "traits":
dirk = Human.new
dirk.extend(actor)
dirk.work
if dirk.income < PITTANCE
dirk.unextend(actor)
dirk.extend(waiter)
dirk.work
end
I don't believe there is an unextend method, however if you're willing to live with some dark magic that I haven't extensively tested:
irb(main):001:0> require 'quasiextender'
=> true
irb(main):002:0> class A
irb(main):003:1> def each
irb(main):004:2> yield 1
irb(main):005:2> end
irb(main):006:1> include QuasiExtender
irb(main):007:1> end
=> A
irb(main):008:0> a = A.new
=> #<A:0x370718>
irb(main):009:0> a.quasi_extend(Enumerable)
=> [Enumerable]
irb(main):010:0> a.map { |x| x.to_s }
=> ["1"]
irb(main):011:0> a.quasi_retract(Enumerable)
=>
irb(main):012:0> a.map { |x| x }
NoMethodError: undefined method `map' for #<A:0x370718 @__quasi_extensions=>
from ./quasiextender.rb:30:in `method_missing'
from (irb):12
And here's the evil code. Its actually not terribly long:
% cat quasiextender.rb
require 'delegate'
module QuasiExtender
def quasi_extend(mod) @__quasi_extensions ||= @__quasi_extensions << mod
end
def quasi_retract(mod) @__quasi_extensions ||= @__quasi_extensions.delete_if { |ext| ext == mod }
end
def method_missing(name, *args, &block) @__quasi_extensions ||=
meth = nil
found_mod = nil @__quasi_extensions.each do |ext|
begin
meth = ext.instance_method(name.to_sym)
found_mod = ext
rescue NameError, NoMethodError
next
else
break
end
end
if meth.nil? # we didn't find it
super
else
sneaky_class = Class.new(SimpleDelegator) {
include(found_mod)
}
sneaky_obj = sneaky_class.new(self)
meth.bind(sneaky_obj).call(*args, &block)
end
end
end
···
On Mar 9, 2006, at 11:03 AM, Dirk van Deun wrote:
A very short and simple question: is there a function to "unextend" an
object ? Obviously it does not exist under that name, nor could I find
anything like it. It would be useful though to do "traits":
dirk = Human.new
dirk.extend(actor)
dirk.work
if dirk.income < PITTANCE
dirk.unextend(actor)
dirk.extend(waiter)
dirk.work
end
class Object
def unextend(mod)
(mod.instance_methods +
mod.private_instance_methods +
mod.protected_instance_methods).uniq.each do |m|
instance_eval "undef #{m}"
end
end
end
% cat unextend
module Actor
def work
"actor"
end
end
module Waiter
def work
"waiter"
end
end
class Human; end
dirk = Human.new
dirk.extend Actor
p dirk.work
begin
dirk = dirk.dup
p dirk.work
rescue
puts "dirk has no job"
end
dirk.extend Waiter
p dirk.work
% ruby unextend
"actor"
dirk has no job
"waiter"
And, to put it in the nice #unextend method:
class Object
def unextend
self.replace dup
end
end
module Actor
def work
"actor"
end
end
module Waiter
def work
"waiter"
end
end
class Human; end
dirk = Human.new
dirk.extend Actor
p dirk.work
begin
dirk.unextend
p dirk.work
rescue
puts "dirk has no job"
end
dirk.extend Waiter
p dirk.work
% ruby unextend
"actor"
dirk has no job
"waiter"
···
On Mar 9, 2006, at 10:03 AM, Dirk van Deun wrote:
A very short and simple question: is there a function to "unextend" an
object ? Obviously it does not exist under that name, nor could I find
anything like it. It would be useful though to do "traits":
dirk = Human.new
dirk.extend(actor)
dirk.work
if dirk.income < PITTANCE
dirk.unextend(actor)
dirk.extend(waiter)
dirk.work
end
I get the feeling that could be dangerous. Since you do it in the context of Object, you may very well delete methods implemented by the object that have the same names as ones in the module. One example I can think of is to_a. There is an Enumerable#to_a which which gives an array after iterating thru an object. Likewise there's an Array#to_a. That method, in object instance_eval'ed the way you have would delete Array's to_a all together. Of course #extend'ing an object apparently overwrittes the old methods anyway (as opposed to including it in the class, which inserts the module into the inheritance chain). At least with my hack when you don't loose method's you've specifically implemented yourself.
···
On Mar 10, 2006, at 8:42 AM, Ross Bamford wrote:
On Fri, 2006-03-10 at 01:03 +0900, Dirk van Deun wrote:
A very short and simple question: is there a function to "unextend" an
object ?
Might this suffice?
class Object
def unextend(mod)
(mod.instance_methods +
mod.private_instance_methods +
mod.protected_instance_methods).uniq.each do |m|
instance_eval "undef #{m}"
end
end
end
: Any idea why not? I currently use extend much like your traits example, and
: will shortly need to be able to reverse it.
I must say I appreciate the QuasiExtender as a useful hack, but it
is a hack, of course. A language feature that manipulates
indirections inside the interpreter instead of adding an extra
layer of indirections would indeed be cooler...
>> A very short and simple question: is there a function to
>> "unextend" an
>> object ?
>
> Might this suffice?
>
> class Object
> def unextend(mod)
> (mod.instance_methods +
> mod.private_instance_methods +
> mod.protected_instance_methods).uniq.each do |m|
> instance_eval "undef #{m}"
> end
> end
> end
>
I get the feeling that could be dangerous.
Certainly. I think 'unextending' is bound to be so - I get the feeling
this may be why it's not already implemented in Ruby
Since you do it in the
context of Object, you may very well delete methods implemented by
the object that have the same names as ones in the module. One
example I can think of is to_a. There is an Enumerable#to_a which
which gives an array after iterating thru an object. Likewise there's
an Array#to_a. That method, in object instance_eval'ed the way you
have would delete Array's to_a all together. Of course #extend'ing
an object apparently overwrittes the old methods anyway (as opposed
to including it in the class, which inserts the module into the
inheritance chain).
Good point.
At least with my hack when you don't loose
method's you've specifically implemented yourself.
But is that really a better place to be? You extend a module, override a
method, then unextend (or whatever) get left with what looks like some
of the module. It's probably pretty likely the new method depends on
other methods from the module, and so is now broken.
Not that I'm disagreeing with you, it's undoubtedly a dangerous thing to
do in any situation. Fortunately, I'm not (yet) forced to include health
warnings on my code
···
On Sat, 2006-03-11 at 02:01 +0900, Logan Capaldo wrote:
On Mar 10, 2006, at 8:42 AM, Ross Bamford wrote:
> On Fri, 2006-03-10 at 01:03 +0900, Dirk van Deun wrote:
A very short and simple question: is there a function to
"unextend" an
object ?
Might this suffice?
class Object
def unextend(mod)
(mod.instance_methods +
mod.private_instance_methods +
mod.protected_instance_methods).uniq.each do |m|
instance_eval "undef #{m}"
end
end
end
I get the feeling that could be dangerous.
Certainly. I think 'unextending' is bound to be so - I get the feeling
this may be why it's not already implemented in Ruby
Since you do it in the
context of Object, you may very well delete methods implemented by
the object that have the same names as ones in the module. One
example I can think of is to_a. There is an Enumerable#to_a which
which gives an array after iterating thru an object. Likewise there's
an Array#to_a. That method, in object instance_eval'ed the way you
have would delete Array's to_a all together. Of course #extend'ing
an object apparently overwrittes the old methods anyway (as opposed
to including it in the class, which inserts the module into the
inheritance chain).
Good point.
At least with my hack when you don't loose
method's you've specifically implemented yourself.
But is that really a better place to be? You extend a module, override a
method, then unextend (or whatever) get left with what looks like some
of the module. It's probably pretty likely the new method depends on
other methods from the module, and so is now broken.
I was thinking more along the lines of implementing the "overriden" version first. My hack only calls module methods if your object doesn't implement them already.
Not that I'm disagreeing with you, it's undoubtedly a dangerous thing to
do in any situation. Fortunately, I'm not (yet) forced to include health
warnings on my code
Ditto
···
On Mar 10, 2006, at 12:18 PM, Ross Bamford wrote:
On Sat, 2006-03-11 at 02:01 +0900, Logan Capaldo wrote:
On Mar 10, 2006, at 8:42 AM, Ross Bamford wrote:
On Fri, 2006-03-10 at 01:03 +0900, Dirk van Deun wrote: