Selective mixins; who can improve on this?

Discussion in 'Ruby' started by Kirk Haines, Aug 1, 2005.

  1. Kirk Haines

    Kirk Haines Guest

    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
     
    Kirk Haines, Aug 1, 2005
    #1
    1. Advertising

  2. Kirk Haines

    Trans Guest

    Kirk Haines wrote:
    > 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.
     
    Trans, Aug 1, 2005
    #2
    1. Advertising

  3. Kirk Haines

    Ara.T.Howard Guest

    On Tue, 2 Aug 2005, Trans wrote:


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

    >

    <snip>
    > 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
    ===============================================================================
     
    Ara.T.Howard, Aug 2, 2005
    #3
  4. Kirk Haines

    Trans Guest

    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.
     
    Trans, Aug 2, 2005
    #4
  5. Kirk Haines

    Paul Brannan Guest

    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
     
    Paul Brannan, Sep 7, 2005
    #5
  6. Paul Brannan wrote:
    > 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
     
    Daniel Berger, Sep 7, 2005
    #6
  7. Kirk Haines

    Paul Brannan Guest

    On Thu, Sep 08, 2005 at 02:52:41AM +0900, Daniel Berger wrote:
    > 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
     
    Paul Brannan, Sep 8, 2005
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Martin Maney

    Calling __init__ for all mixins

    Martin Maney, Aug 18, 2004, in forum: Python
    Replies:
    4
    Views:
    418
    Michael Hudson
    Aug 19, 2004
  2. Joshua Spoerri

    mixins that don't down-call?

    Joshua Spoerri, Aug 10, 2005, in forum: Python
    Replies:
    0
    Views:
    280
    Joshua Spoerri
    Aug 10, 2005
  3. mk
    Replies:
    1
    Views:
    453
    Bruno Desthuilliers
    Feb 17, 2010
  4. Daniel

    Mixins and overloading

    Daniel, Jul 15, 2011, in forum: C++
    Replies:
    1
    Views:
    244
    Sunil Varma
    Jul 15, 2011
  5. Matt

    Python Mixins

    Matt, Sep 22, 2011, in forum: Python
    Replies:
    9
    Views:
    835
    Terry Reedy
    Sep 25, 2011
Loading...

Share This Page