Finding one's self in the strangest places

(7rans) #1

Hello,

Thought this was interesting for a numbner of reasons, not the least of
which is that I am suprised it even works. What it is is whay to do
dynamic module mixins, i.e. including modules with macro-like changes
in behavior. Note that the first three lines simply add those well
known methods to the Binding class.

  require 'nano/binding/self'
  require 'nano/binding/%5B%5D%3D' # []=
  require 'nano/binding/local_variables'

  class Module

    def module_options
      @module_options ||= {}
    end

    alias_method :include_without_options, :include

    def include(*args)
      base = self
      options = args.last.is_a?(Hash) ? args.pop : {}
      mods = args.collect do |mod|
        mod.append_dynamic_features( self, options )
        mod
      end
      include_without_options(*mods)
    end

    # Note: Is this the best name for this callback?
    def append_dynamic_features( base, options )
      mod = self
      base.module_options[mod] = options
    end

    # vars is just a dummy slot, does nothing
    def dynamic_mixin( space, *vars )
      mod = space.self
      space.local_variables.each { |v|
        default = space[v.to_sym]
        space[v.to_sym] = lambda { |base|
base.class.module_options[mod][v.to_sym] || default }
      }
    end

  end

  # try it

  if $0 == __FILE__

    module MyDynamix

      dynamic_mixin( binding, name='Tom' ) # name is local var

      define_method( :hello ) {
        puts "Hello from #{name[self]}"
      }

      define_method( :goodbye ) {
        puts "Goodbye from #{name[self]}"
      }

    end

    class MyClass
      include MyDynamix, :name => 'George'
    end

    m = MyClass.new
    m.hello #=> Hello from George
    m.goodbye #=> Hello from George

    class MyClass2
      include MyDynamix
    end

    m = MyClass2.new
    m.hello #=> Hello from Tom
    m.goodbye #=> Hello from Tom

  end

What amazes me about this is that 'name' in 'name[self]' is a local
variable assigned to a lambda, yet 'self' is not and rather belongs to
the initialized instance of the class. How is that possible? How is
execution of the lambda deferred til later? I think this is a
facinating example of Ruby's "juggling" abilities.

Also, it would be nice to be able to take this one step further, if it
were possible NOT to have to explictly pass 'binding' via
#dyanmic_mixin or pass 'self' via the local var lambda, then it would
be perfect.

T.

(Robert) #2

Trans wrote:

What amazes me about this is that 'name' in 'name[self]' is a local
variable assigned to a lambda, yet 'self' is not and rather belongs to
the initialized instance of the class. How is that possible? How is
execution of the lambda deferred til later? I think this is a
facinating example of Ruby's "juggling" abilities.

The concept is generally known as "closure". The closure captures variable
bindings so you can access them later:

12:53:16 [source]: ruby -e 'x=10;f=lambda { x }; p f.call;x=20;p f.call'
10
20
12:53:23 [source]:

Also, it would be nice to be able to take this one step further, if it
were possible NOT to have to explictly pass 'binding' via
#dyanmic_mixin or pass 'self' via the local var lambda, then it would
be perfect.

There is a module that allows access to the bindinf of the caller. This
would help you as it would make passing a binding explicit unnecessary.

Kind regards

    robert

(7rans) #3

Robert Klemme wrote:

The concept is generally known as "closure". The closure captures variable
bindings so you can access them later:

Right. In this case it just seems more facinating in how the two
closure-spaces (I don't know the proper terminology) are intermingling.

12:53:16 [source]: ruby -e 'x=10;f=lambda { x }; p f.call;x=20;p f.call'
10
20
12:53:23 [source]:

> Also, it would be nice to be able to take this one step further, if it
> were possible NOT to have to explictly pass 'binding' via
> #dyanmic_mixin or pass 'self' via the local var lambda, then it would
> be perfect.

There is a module that allows access to the binding of the caller. This
would help you as it would make passing a binding explicit unnecessary.

Thanks. Binding.of_caller is of some, but limited use. Not sure how to
rid '[self]' with it. In playing with this I again find another of
these methods whose "receiver" is actually the point-of-execution, in
this case #binding. (I was recently talking Module.nesting which is one
too). Yet with #binding, it sure would be nice if it weren't so. Then
Binding.of_caller wouldn't be needed, and one could just do
binding_of_caller = mod.binding.

T.