P
Paul Brannan
Rich and Nathaniel and I were standing around at the conference this
weekend talking about Nathaniel's idea for implementing namespaces in
pure ruby, just so ruby users could play around with them until we get
them in ruby 2.0 (or maybe 1.9.1). Here's my first pass at an
implementation of that idea. It uses a slightly modified version of
Florian Gross's Binding.of_caller method.
The idea is pretty simple; each scope contains two local variables
called __ns (which namespace is currently being modified) and __use
(which namespaces are searched during method invocation). When a method
is added, a proxy method is created that does the method searching. No
method caching is done, but that could be implemented. The global
namespace is known as :__global.
The syntax is something like this:
namespace :namespace do
# methods created in this scope will be added to :namespace
end
use :namespace do
# methods called in this scope will be searched in :namespace first,
# then :__global.
end
Hopefully someone can take this and extend it to properly handle
singleton methods and inheritance.
There is also one bug that I don't know how to address:
class Foo; def foo; puts "global namespace"; end; end
namespace :n { class Foo; def foo; puts "namespace n"; end; end }
p = proc { Foo.new.foo }
use :n { p.call }
This should print "global namespace" but currently prints "namespace n".
The problem is that both the outer scope and the inner scope share the
same __use variable, and I don't know any good way to actually make the
variable truly block-local. I'm all ears if anyone knows how to fix
this.
Paul
---
require 'thread'
def caller_binding(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
result, error = callcc do |cc|
tracer = lambda do |*args|
type, context = args[0], args[4]
if type == "return"
count += 1
if count == 2
set_trace_func(nil)
cc.call(eval("binding", context), nil)
end
elsif type != "line"
set_trace_func(nil)
cc.call(nil, lambda { raise(ArgumentError, "unable to get binding" ) })
end
end
set_trace_func(tracer)
return nil
end
Thread.critical = false
error.call if error
yield result
end
def method_table
h = {}
methods.each do |name|
h[name] = method(name)
end
return h
end
def namespace(ns)
caller_binding do |b|
old_ns = eval("defined?(__ns) ? __ns : nil", b)
eval("__ns = #{ns.inspect}", b)
eval("__mt ||= []; __mt << method_table", b)
begin
use(ns) do
yield
end
ensure
if old_ns then
eval("__ns = #{old_ns.inspect}", b)
else
eval("__ns = nil", b)
end
eval("__mt.pop", b)
end
end
end
def use(ns)
caller_binding do |b|
eval("__use ||= []; __use << #{ns.inspect}", b)
begin
yield
ensure
eval("__use.pop", b)
end
end
end
class Object
# TODO: need singleton_method_added too
def self.method_added(name)
caller_binding do |b|
in_ma = eval("defined?(__in_ma) ? true : false", b)
return if in_ma
ns = eval("defined?(__ns) ? __ns : nil", b)
ns ||= :__global
mt = class << self; @mt ||= {}; end
mt[ns] = instance_method(name)
undef_method name
self.class_eval <<-END
__in_ma = true
def #{name}(*args, &block)
caller_binding do |b|
use = eval("defined?(__use) ? __use : nil", b)
use ||= []
use = use.reverse + [:__global]
use.each do |ns|
# TODO: also search up the inheritance hierarchy
mt = class << self.class; @mt ||= {}; end
u = mt[ns]
next if not u
m = u.bind(self)
return m.call(*args, &block)
end
raise NoMethodError, "undefined method `#{name}' for TODO"
end
end
END
end
end
end
class Foo
namespace :n do
def foo; 42; end
end
namespace :m do
def foo; 10; end
end
def foo; 1; end
end
def bar
p Foo.new.foo
end
use :n do
p Foo.new.foo #=> 42 (namespace n)
use :m do
p Foo.new.foo #=> 10 (namespace m)
end
use :z do
p Foo.new.foo #=> 42 (namespace n)
end
bar #=> 1 (global namespace)
end
use :z do
p Foo.new.foo #=> 1 (global namespace)
end
p Foo.new.foo #=> 1 (global namespace)
weekend talking about Nathaniel's idea for implementing namespaces in
pure ruby, just so ruby users could play around with them until we get
them in ruby 2.0 (or maybe 1.9.1). Here's my first pass at an
implementation of that idea. It uses a slightly modified version of
Florian Gross's Binding.of_caller method.
The idea is pretty simple; each scope contains two local variables
called __ns (which namespace is currently being modified) and __use
(which namespaces are searched during method invocation). When a method
is added, a proxy method is created that does the method searching. No
method caching is done, but that could be implemented. The global
namespace is known as :__global.
The syntax is something like this:
namespace :namespace do
# methods created in this scope will be added to :namespace
end
use :namespace do
# methods called in this scope will be searched in :namespace first,
# then :__global.
end
Hopefully someone can take this and extend it to properly handle
singleton methods and inheritance.
There is also one bug that I don't know how to address:
class Foo; def foo; puts "global namespace"; end; end
namespace :n { class Foo; def foo; puts "namespace n"; end; end }
p = proc { Foo.new.foo }
use :n { p.call }
This should print "global namespace" but currently prints "namespace n".
The problem is that both the outer scope and the inner scope share the
same __use variable, and I don't know any good way to actually make the
variable truly block-local. I'm all ears if anyone knows how to fix
this.
Paul
---
require 'thread'
def caller_binding(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
result, error = callcc do |cc|
tracer = lambda do |*args|
type, context = args[0], args[4]
if type == "return"
count += 1
if count == 2
set_trace_func(nil)
cc.call(eval("binding", context), nil)
end
elsif type != "line"
set_trace_func(nil)
cc.call(nil, lambda { raise(ArgumentError, "unable to get binding" ) })
end
end
set_trace_func(tracer)
return nil
end
Thread.critical = false
error.call if error
yield result
end
def method_table
h = {}
methods.each do |name|
h[name] = method(name)
end
return h
end
def namespace(ns)
caller_binding do |b|
old_ns = eval("defined?(__ns) ? __ns : nil", b)
eval("__ns = #{ns.inspect}", b)
eval("__mt ||= []; __mt << method_table", b)
begin
use(ns) do
yield
end
ensure
if old_ns then
eval("__ns = #{old_ns.inspect}", b)
else
eval("__ns = nil", b)
end
eval("__mt.pop", b)
end
end
end
def use(ns)
caller_binding do |b|
eval("__use ||= []; __use << #{ns.inspect}", b)
begin
yield
ensure
eval("__use.pop", b)
end
end
end
class Object
# TODO: need singleton_method_added too
def self.method_added(name)
caller_binding do |b|
in_ma = eval("defined?(__in_ma) ? true : false", b)
return if in_ma
ns = eval("defined?(__ns) ? __ns : nil", b)
ns ||= :__global
mt = class << self; @mt ||= {}; end
mt[ns] = instance_method(name)
undef_method name
self.class_eval <<-END
__in_ma = true
def #{name}(*args, &block)
caller_binding do |b|
use = eval("defined?(__use) ? __use : nil", b)
use ||= []
use = use.reverse + [:__global]
use.each do |ns|
# TODO: also search up the inheritance hierarchy
mt = class << self.class; @mt ||= {}; end
u = mt[ns]
next if not u
m = u.bind(self)
return m.call(*args, &block)
end
raise NoMethodError, "undefined method `#{name}' for TODO"
end
end
END
end
end
end
class Foo
namespace :n do
def foo; 42; end
end
namespace :m do
def foo; 10; end
end
def foo; 1; end
end
def bar
p Foo.new.foo
end
use :n do
p Foo.new.foo #=> 42 (namespace n)
use :m do
p Foo.new.foo #=> 10 (namespace m)
end
use :z do
p Foo.new.foo #=> 42 (namespace n)
end
bar #=> 1 (global namespace)
end
use :z do
p Foo.new.foo #=> 1 (global namespace)
end
p Foo.new.foo #=> 1 (global namespace)