Selective mixins; who can improve on this?

K

Kirk Haines

So, on #ruby-lang an hour ago, Daniel Berger asked for ideas to make some
non-working code of his work. He's trying to come up with a way to
selectively mixin methods from a module into a class.

After some tinkering, I came up with this working, but ugly and definitely
improveable code to do it:

class Module
def use(mod, *methods)
c = Class.new
# Make a singleton that mixes everything in, so that we can cherrypick.
eval("class << c; include #{mod}; end")

methods.each do |m|
# There has to be a more elegant way to do this.
m_sym = m.to_sym
mc_sym = m.to_s.upcase.to_sym
um = c.method(m_sym)
self.const_set(mc_sym,um)
self.class_eval("def #{m.to_s}; #{mc_sym.to_s}.call; end")
end
end
end


module Foo
def a; 'abc'; end
def z; 'xyz'; end
end

class Bing
use(Foo, :a, 'z')
end

x = Bing.new
p x.a
p x.z


There has to be a more elegant way to do this. What is it?


Kirk Haines
 
T

Trans

Kirk said:
So, on #ruby-lang an hour ago, Daniel Berger asked for ideas to make some
non-working code of his work. He's trying to come up with a way to
selectively mixin methods from a module into a class.

After some tinkering, I came up with this working, but ugly and definitely
improveable code to do it:

class Module
def use(mod, *methods)
c = Class.new
# Make a singleton that mixes everything in, so that we can cherrypick.
eval("class << c; include #{mod}; end")

methods.each do |m|
# There has to be a more elegant way to do this.
m_sym = m.to_sym
mc_sym = m.to_s.upcase.to_sym
um = c.method(m_sym)
self.const_set(mc_sym,um)
self.class_eval("def #{m.to_s}; #{mc_sym.to_s}.call; end")
end
end
end


module Foo
def a; 'abc'; end
def z; 'xyz'; end
end

class Bing
use(Foo, :a, 'z')
end

x = Bing.new
p x.a
p x.z


There has to be a more elegant way to do this. What is it?

I'm afraid that is not doing what you expect it too. Try:

def a; p self; end

and you will see that self is not a Bing.

There is no _simple_ solution to this. Ruby is picky about where its
methods come from and where they can thus go to. Try playing with
#method, Class#instance_method and #bind and you'll see what I mean.
BUT I have done what you ask, as I have/am considering using something
like this for Ruby Facets. Off the top of my head (so forgive any
little bugs) like:

class Module
METHINDEX = {}

def index_def( msym, &mdef )
METHINDEX[[self,:"#{msym}"]] = mdef
end

def use(mod, *methods)
methods.each { |msym| self.class_eval
&METHINDEX[[mod,:"#{msym}"]] }
end
end

module Foo
index_def( :a ) {
def a; p self ; end
}

index_def( :z ) {
def z; p "z" ; end
}
end

class Bing
use Foo, :a, 'z'
end

The block is needed to protect the method defintion from binding to an
unusable location.

Am interested in other people's solutions too.

T.
 
A

Ara.T.Howard

On Tue, 2 Aug 2005, Trans wrote:


Am interested in other people's solutions too.

harp:~ > cat a.rb
class Module
def import desc
desc.each do |a_module, a_list|
m = Module::new
m.module_eval do
include a_module
a = a_module.instance_methods
b = [ a_list ].flatten.map{|b| b.to_s}
(a - b).each{|um| undef_method um}
end
module_eval{ include m }
end
end
end

module Foo
def foo
p self
p 42
end
def foobar
p self
p @foobar
end
end

module Bar
def bar
p self
p 42.0
end
def barfoo
p self
p @barfoo
end
def not_imported
end
end

class C
import Foo => %w( foo foobar ),
Bar => %w( bar barfoo )

def initialize
@foobar = 'foobar'
@barfoo = 'barfoo'
end
end

c = C::new

c.foo
c.bar
c.foobar
c.barfoo
c.not_imported


harp:~ > ruby a.rb
#<C:0xb75d060c @barfoo="barfoo", @foobar="foobar">
42
#<C:0xb75d060c @barfoo="barfoo", @foobar="foobar">
42.0
#<C:0xb75d060c @barfoo="barfoo", @foobar="foobar">
"foobar"
#<C:0xb75d060c @barfoo="barfoo", @foobar="foobar">
"barfoo"
a.rb:56: undefined method `not_imported' for #<C:0xb75d060c @barfoo="barfoo", @foobar="foobar"> (NoMethodError)


seems to work. i'm probably missing something though...

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
T

Trans

a,

You are clever. I give you that. Building an anonymous module to
include the target module and then undefining the methods _unwanted_...
yes, it should work. But it ain't elegant! :)

T.
 
P

Paul Brannan

Here's my solution:

require 'nodewrap'

class Module
def use(mod, *methods)
methods.each do |method|
sym = method.to_sym
body = mod.instance_method(sym).body
add_method(sym, body, 0)
end
end
end

Be careful,

Paul
 
D

Daniel Berger

Paul said:
Here's my solution:

require 'nodewrap'

class Module
def use(mod, *methods)
methods.each do |method|
sym = method.to_sym
body = mod.instance_method(sym).body
add_method(sym, body, 0)
end
end
end

This doesn't work for me. There appears to be no UnboundMethod#body
method, even after requiring nodewrap.

What's missing?

Regards,

Dan
 
P

Paul Brannan

This doesn't work for me. There appears to be no UnboundMethod#body
method, even after requiring nodewrap.

What's missing?

The latest version of nodewrap is missing. :)

I fixed this a year ago but haven't done a release yet. Get nodewrap
from cvs and you'll get UnboundMethod#body (along with lots of other
cool prizes).

Paul
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top