Forum: Ruby unextend ?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
unknown (Guest)
on 2006-03-09 18:06
(Received via mailing list)
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

Dirk van Deun
Logan C. (Guest)
on 2006-03-09 23:37
(Received via mailing list)
On Mar 9, 2006, at 11:03 AM, Dirk van Deun wrote:

>   dirk.extend(waiter)
>   dirk.work
> end
>
> Dirk van Deun
> --
> Ceterum censeo Redmond delendum
>

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
itsme213 (Guest)
on 2006-03-10 03:49
(Received via mailing list)
"Logan C." <removed_email_address@domain.invalid> wrote

> I don't believe there is an unextend method

Any idea why not? I currently use extend much like your traits example,
and
will shortly need to be able to reverse it.
Logan C. (Guest)
on 2006-03-10 05:29
(Received via mailing list)
On Mar 9, 2006, at 8:48 PM, itsme213 wrote:

> Any idea why not?

I imagine its a combination of implementation details and the
semantics of extend. I couldn't say authortativly though

> I currently use extend much like your traits example, and
> will shortly need to be able to reverse it.

traits example?
unknown (Guest)
on 2006-03-10 14:56
(Received via mailing list)
: > I don't believe there is an unextend method

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

Dirk van Deun
Ross B. (Guest)
on 2006-03-10 15:45
(Received via mailing list)
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

class A
  def each; yield 1; end
end

a = A.new

a.extend(Enumerable)
puts a.map { |e| e + 1 }
# => 2

a.unextend(Enumerable)
puts a.map { |e| e + 1 }
# => -:17: undefined method `map' for #<A:0xb7f837a0> (NoMethodError)
Logan C. (Guest)
on 2006-03-10 19:04
(Received via mailing list)
On Mar 10, 2006, at 8:42 AM, Ross B. wrote:

>      mod.private_instance_methods +
> a = A.new
> Ross B. - removed_email_address@domain.invalid
>
>

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.
Ross B. (Guest)
on 2006-03-10 19:19
(Received via mailing list)
On Sat, 2006-03-11 at 02:01 +0900, Logan C. wrote:
> >   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 ;)
Logan C. (Guest)
on 2006-03-10 19:43
(Received via mailing list)
On Mar 10, 2006, at 12:18 PM, Ross B. wrote:

>>> class Object
>> I get  the feeling that could be dangerous.
>> have would delete Array's to_a all together. Of course #extend'ing
> 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 ;)
Jim F. (Guest)
on 2006-03-11 15:26
(Received via mailing list)
Hi

Have you tried #dup?

% 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:

>   dirk.extend(waiter)
>   dirk.work
> end
>
> Dirk van Deun
> --
> Ceterum censeo Redmond delendum
>

Jim F.
Robert H. (Guest)
on 2015-03-22 02:00
10 years since ... anyone knows if there are plans to allow that in Ruby
3.0 perhaps?
Robert K. (Guest)
on 2015-03-22 18:59
Robert H. wrote in post #1170668:
> 10 years since ... anyone knows if there are plans to allow that in Ruby
> 3.0 perhaps?

I'm not convinced that it's a good idea: you will create a situation
where some type properties of an object hold which are then later
removed.  Example:

class SomeClass
  def add(item)
    raise ArgumentError, "Wrong type" unless Enumerable === item
    (@items ||=[]) << item
    self
  end

  def show_all
    @items and @items.each {|item| p item.max}
  end
end

x = SomeClass.new

y = Object.new.extend Enumerable
def y.each; yield 12; yield 32; self end

x.add y
y.unextend Enumerable
x.show_all # boom

If someone needs the capability that objects can have changing roles
then I think it better to implement that explicitly, e.g. by a variant
of state pattern.
Scott S. (Guest)
on 2015-03-23 02:49
To "unextend" some object, don't you just need to change it's place in
the inheritance hierarchy? iow, change/replace/remove/delegate it's
singleton class?

Ruby 2 introduced refinements. Will they server your needs?
This topic is locked and can not be replied to.