J
Jeff Mitchell
Happy Holidays!
It always concerned me that instance variables of a class propagate to
its subclasses and that instance variables of a module propagate to
classes which include it.
This lack of private variables caused me great unrest until I learned
a little more ruby and then realized private variables can be
implemented in ruby itself!
What do you think? Criticism is welcome; this one of my first
non-hello-world ruby scripts so be nice And thanks to the irc folks
who patiently answered my questions.
#----------------------------------
# synopsis
#----------------------------------
require 'wrap'
module A
def a_f
@x = 11
end
def a_x
@x
end
end
module B
def b_f
@x = 22
end
def b_x
@x
end
end
class C
include Wrap(A)
include Wrap(B)
end
c = C.new
c.a_f
c.b_f
puts "A's @x is #{c.a_x}"
puts "B's @x is #{c.b_x}"
# => A's @x is 11
# => B's @x is 22
class P
def initialize
@x = 33
end
def p_x
@x
end
end
class Q < Wrap P
def initialize
super()
@x = 44
end
def q_x
@x
end
end
q = Q.new
puts "P's @x is #{q.p_x}"
puts "Q's @x is #{q.q_x}"
# => P's @x is 33
# => Q's @x is 44
#----------------------------------
# wrap.rb
#
# privatize data using delegation to an anonymous class.
#----------------------------------
def Wrap mod
if mod.class == Module
wrap_module(mod)
elsif mod.class == Class
wrap_class(mod)
else
raise "expected Class or Module for Wrap: got #{mod.class}"
end
end
def wrap_module mod
wrap = Module.new
classname = "Private__class#{wrap.object_id}"
instance = "@__private#{wrap.object_id}"
wrap.const_set(classname.intern, Class.new{ include mod })
mod.constants.each do |name|
wrap.const_set(name.intern, mod.const_get(name.intern))
end
create = Proc.new do |visibility, method|
next if method =~ /^__/
wrap.class_eval <<-end_eval
def #{method}(*args, &block)
#{instance} ||= #{classname}.new
#{instance}.__send__#{method}, *args, &block)
end
end_eval
wrap.__send__(visibility, method)
end
mod.public_instance_methods(true).each{|m|create.callpublic,m)}
mod.protected_instance_methods(true).each{|m|create.callprotected,m)}
mod.private_instance_methods(true).each{|m|create.callprotected,m)}
wrap
end
def wrap_class aclass
wrap = Class.new
instance = "@__private#{wrap.object_id}"
aclass.constants.each do |name|
wrap.const_set(name.intern, aclass.const_get(name.intern))
end
aclass.singleton_methods(true).each do |method|
wrap.class_eval <<-end_eval
def self.#{method}(*args, &block)
#{aclass.name}.#{method}(*args, &block)
end
end_eval
end
wrap.class_eval <<-end_eval
def initialize(*args, &block)
#{instance} = #{aclass.name}.new(*args, &block)
end
end_eval
create = Proc.new do |visibility, method|
next if method == "initialize"
next if method =~ /^__/
wrap.class_eval <<-end_eval
def #{method}(*args, &block)
#{instance}.__send__#{method}, *args, &block)
end
end_eval
wrap.__send__(visibility, method)
end
aclass.ancestors.each do |c|
break if c == Object
c.public_instance_methods(false).each{|m|create.callpublic,m)}
c.protected_instance_methods(false).each{|m|create.callprotected,m)}
c.private_instance_methods(false).each{|m|create.callprivate,m)}
end
wrap
end
#----------------------------------
It's both like and unlike delegation. With delegation, you pass an
object explicitly and the dual-object relationship is part of the
design.
With wrapping there is no object explicitly passed and the delegation
is hidden; there are still two objects but you pretend one of them
doesn't exist. But essentially it's just a notational convenience for
a certain form of delegation.
I see some specialized things would make it faster, for example direct
evals of public methods (no __send__) and re-evaling after the first
method call to remove the nil test for module methods.
Anyway, all of this may be too silly. It's really an anecdote of
sorts. I was surprised that ruby was flexible enough that I could
seemingly add (or simulate) a new language construct from inside the
language itself.
-Jeff (quix)
It always concerned me that instance variables of a class propagate to
its subclasses and that instance variables of a module propagate to
classes which include it.
This lack of private variables caused me great unrest until I learned
a little more ruby and then realized private variables can be
implemented in ruby itself!
What do you think? Criticism is welcome; this one of my first
non-hello-world ruby scripts so be nice And thanks to the irc folks
who patiently answered my questions.
#----------------------------------
# synopsis
#----------------------------------
require 'wrap'
module A
def a_f
@x = 11
end
def a_x
@x
end
end
module B
def b_f
@x = 22
end
def b_x
@x
end
end
class C
include Wrap(A)
include Wrap(B)
end
c = C.new
c.a_f
c.b_f
puts "A's @x is #{c.a_x}"
puts "B's @x is #{c.b_x}"
# => A's @x is 11
# => B's @x is 22
class P
def initialize
@x = 33
end
def p_x
@x
end
end
class Q < Wrap P
def initialize
super()
@x = 44
end
def q_x
@x
end
end
q = Q.new
puts "P's @x is #{q.p_x}"
puts "Q's @x is #{q.q_x}"
# => P's @x is 33
# => Q's @x is 44
#----------------------------------
# wrap.rb
#
# privatize data using delegation to an anonymous class.
#----------------------------------
def Wrap mod
if mod.class == Module
wrap_module(mod)
elsif mod.class == Class
wrap_class(mod)
else
raise "expected Class or Module for Wrap: got #{mod.class}"
end
end
def wrap_module mod
wrap = Module.new
classname = "Private__class#{wrap.object_id}"
instance = "@__private#{wrap.object_id}"
wrap.const_set(classname.intern, Class.new{ include mod })
mod.constants.each do |name|
wrap.const_set(name.intern, mod.const_get(name.intern))
end
create = Proc.new do |visibility, method|
next if method =~ /^__/
wrap.class_eval <<-end_eval
def #{method}(*args, &block)
#{instance} ||= #{classname}.new
#{instance}.__send__#{method}, *args, &block)
end
end_eval
wrap.__send__(visibility, method)
end
mod.public_instance_methods(true).each{|m|create.callpublic,m)}
mod.protected_instance_methods(true).each{|m|create.callprotected,m)}
mod.private_instance_methods(true).each{|m|create.callprotected,m)}
wrap
end
def wrap_class aclass
wrap = Class.new
instance = "@__private#{wrap.object_id}"
aclass.constants.each do |name|
wrap.const_set(name.intern, aclass.const_get(name.intern))
end
aclass.singleton_methods(true).each do |method|
wrap.class_eval <<-end_eval
def self.#{method}(*args, &block)
#{aclass.name}.#{method}(*args, &block)
end
end_eval
end
wrap.class_eval <<-end_eval
def initialize(*args, &block)
#{instance} = #{aclass.name}.new(*args, &block)
end
end_eval
create = Proc.new do |visibility, method|
next if method == "initialize"
next if method =~ /^__/
wrap.class_eval <<-end_eval
def #{method}(*args, &block)
#{instance}.__send__#{method}, *args, &block)
end
end_eval
wrap.__send__(visibility, method)
end
aclass.ancestors.each do |c|
break if c == Object
c.public_instance_methods(false).each{|m|create.callpublic,m)}
c.protected_instance_methods(false).each{|m|create.callprotected,m)}
c.private_instance_methods(false).each{|m|create.callprivate,m)}
end
wrap
end
#----------------------------------
It's both like and unlike delegation. With delegation, you pass an
object explicitly and the dual-object relationship is part of the
design.
With wrapping there is no object explicitly passed and the delegation
is hidden; there are still two objects but you pretend one of them
doesn't exist. But essentially it's just a notational convenience for
a certain form of delegation.
I see some specialized things would make it faster, for example direct
evals of public methods (no __send__) and re-evaling after the first
method call to remove the nil test for module methods.
Anyway, all of this may be too silly. It's really an anecdote of
sorts. I was surprised that ruby was flexible enough that I could
seemingly add (or simulate) a new language construct from inside the
language itself.
-Jeff (quix)