[CHALLENGE] better alias_method

A

ara.t.howard

i've been wanting a better alias_method for quite some time. essentially i'd
like a way out of the trap where executing

alias_method '__fubar__', 'fubar'

goes haywire when __fubar__ already exists. we've all seen it happen before.

anyhow. the interface it'd like would be


class C
def m() 'a' end

push_method 'm'

def m() super + 'b' end
end

p C.new.m #=> 'ab'


what i've got is quite close, but no cigar. it has a fundemental problem with
the way ruby scopes super which i'm too tired atttm to figure out. i'm hoping
i can go to bed and wake up to a nice patch ;-) here's what i've got:


harp:~ > cat a.rb
class Module
def push_method m
this = self

include Module.new{
@m = this.instance_method m

this.module_eval{ remove_method m }

module_eval <<-code
def #{ m }(*a, &b)
um = ObjectSpace._id2ref #{ @m.object_id }
um.bind(self).call *a, &b
end
code
}
end
end

class C
def m
'a'
end
p new.m #=> 'a'


push_method 'm'


def m
super + 'b'
end
p new.m #=> 'ab'


push_method 'm'


def m
super + 'c'
end
p new.m #=> 'abc'
end



harp :~ > ruby a.rb
"a"
"ab"
a.rb:31:in `m': stack level too deep (SystemStackError)
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
... 2343 levels...
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:40:in `m'
from a.rb:42


have at it - i'll be back in 8 hrs. ;-)


-a
 
L

Logan Capaldo

i've been wanting a better alias_method for quite some time.
essentially i'd
like a way out of the trap where executing

alias_method '__fubar__', 'fubar'

goes haywire when __fubar__ already exists. we've all seen it
happen before.

anyhow. the interface it'd like would be


class C
def m() 'a' end

push_method 'm'

def m() super + 'b' end
end

p C.new.m #=> 'ab'


what i've got is quite close, but no cigar. it has a fundemental
problem with
the way ruby scopes super which i'm too tired atttm to figure out.
i'm hoping
i can go to bed and wake up to a nice patch ;-) here's what i've got:


harp:~ > cat a.rb
class Module
def push_method m
this = self

include Module.new{
@m = this.instance_method m

this.module_eval{ remove_method m }

module_eval <<-code
def #{ m }(*a, &b)
um = ObjectSpace._id2ref #{ @m.object_id }
um.bind(self).call *a, &b
end
code
}
end
end

class C
def m
'a'
end
p new.m #=> 'a'


push_method 'm'


def m
super + 'b'
end
p new.m #=> 'ab'


push_method 'm'


def m
super + 'c'
end
p new.m #=> 'abc'
end



harp :~ > ruby a.rb
"a"
"ab"
a.rb:31:in `m': stack level too deep (SystemStackError)
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
... 2343 levels...
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:40:in `m'
from a.rb:42


have at it - i'll be back in 8 hrs. ;-)

I have this, it takes a different approach though:

module Patchable
module ClassMethods
def patch(method_name = nil, &new_body)
if method_name
method_name = method_name.to_sym
pre_patched_versions[method_name] = instance_method
(method_name)
define_method(method_name, &new_body)
else
klass = Class.new
imeths = klass.instance_methods
klass.class_eval(&new_body)
new_meths = klass.instance_methods - imeths
new_meths.each do |m|
pre_patched_versions[m.to_sym] = instance_method(m)
end
class_eval(&new_body)
end
self
end

def pre_patched_versions
@pre_patched_versions ||= {}
end
end

def hyper(*args, &block)
meth_name = caller[0][/`([^']+)'/, 1].to_sym
self.class.pre_patched_versions[meth_name].bind(self).call
(*args, &block)
end

def self.included(other)
other.extend(ClassMethods)
end
end

class C
include Patchable

def m
'a'
end

p new.m
patch do
def m
hyper + 'b'
end
end

p new.m

patch do
def m
hyper + 'c'
end
end

#p new.m doesn't work, infinite recursion
end

Darn. You seem to have made me discover a bug in my impl. It doesn't
work for more than one level of patching per method. Well maybe
someone will give me a patch too ;)
 
K

Ken Bloom

i've been wanting a better alias_method for quite some time. essentially i'd
like a way out of the trap where executing

alias_method '__fubar__', 'fubar'

goes haywire when __fubar__ already exists. we've all seen it happen before.

anyhow. the interface it'd like would be

It would be nice to have this built in to the language -- a way to blur
the difference between overriding an inherited method and overriding a
method defined on the current class, so that `super' would call whatever
the previous version of the was.

Try this idea on for size. I wrote it a few days ago. Look at the
unit tests at the end to see how it's intended to be used. I think a
little syntactic sugar would be nice, but it gets the job done.

class Module
private
def override(method_name,&block)
begin
#case 1: I'm overriding a method defined on this object
old_method=instance_method(method_name)
alias_for_old=unused_alias
alias_method alias_for_old, method_name
rescue NameError
begin
#case 2: I'm overriding a method that I inherit from
old_method=superclass.instance_method(method_name)
rescue NameError
#case 3: I'm not overriding anything. Just use a simple
#function that we can call without side effects
old_method=Object.instance_method:)nil?)
end
end
define_method(method_name) do |*args|
block.call(old_method.bind(self),*args)
end
end
def unused_alias
newalias=nil
while newalias==nil or instance_methods.include?(newalias)
newalias=:"__kenoverride__#{rand(10**20)}__"
end
newalias
end
end

if __FILE__==$0
require 'test/unit'
class TestOverride < Test::Unit::TestCase
def setup
eval <<-"end;"
class A
def foo; "a"; end
end
end;
end


def test_alias
assert_nothing_raised do
eval <<-"end;"
class A
override:)foo){|old| old.call+"b"}
end
end;
end

assert_equal A.new.foo,"ab"

end

def test_inherit
assert_nothing_raised do
eval <<-"end;"
class B<A
override:)foo){|old| "c"+old.call }
end
end;
end

#I don't want to rely on whether test_inherit or test_alias
#comes first, so I'll test for the results of both orders.
assert ["ca","cab"].include?(B.new.foo)
end

#I'm not really sure whether this is the appropriate behavior
#or whether throwing an exception is more appropriate.
def test_neither
assert_nothing_raised do
eval <<-"end;"
class C
override:)foo){|old| "d" }
end
end;
end

assert_equal C.new.foo,"d"
end
end
end
 
K

Ken Bloom

i've been wanting a better alias_method for quite some time. essentially i'd
like a way out of the trap where executing

alias_method '__fubar__', 'fubar'

goes haywire when __fubar__ already exists. we've all seen it happen before.

anyhow. the interface it'd like would be

It would be nice to have this built in to the language -- a way to blur
the difference between overriding an inherited method and overriding a
method defined on the current class, so that `super' would call whatever
the previous version of the was.

Try this idea on for size. I wrote it a few days ago. Look at the
unit tests at the end to see how it's intended to be used. I think a
little syntactic sugar would be nice, but it gets the job done.

class Module
private
def override(method_name,&block)
begin
#case 1: I'm overriding a method defined on this object
old_method=instance_method(method_name)
alias_for_old=unused_alias
alias_method alias_for_old, method_name
rescue NameError
begin
#case 2: I'm overriding a method that I inherit from
old_method=superclass.instance_method(method_name)
rescue NameError
#case 3: I'm not overriding anything. Just use a simple
#function that we can call without side effects
old_method=Object.instance_method:)nil?)
end
end
define_method(method_name) do |*args|
block.call(old_method.bind(self),*args)
end
end
def unused_alias
newalias=nil
while newalias==nil or instance_methods.include?(newalias)
newalias=:"__kenoverride__#{rand(10**20)}__"
end
newalias
end
end

if __FILE__==$0
require 'test/unit'
class TestOverride < Test::Unit::TestCase
def setup
eval <<-"end;"
class A
def foo; "a"; end
end
end;
end


def test_alias
assert_nothing_raised do
eval <<-"end;"
class A
override:)foo){|old| old.call+"b"}
end
end;
end

assert_equal A.new.foo,"ab"

end

def test_inherit
assert_nothing_raised do
eval <<-"end;"
class B<A
override:)foo){|old| "c"+old.call }
end
end;
end

#I don't want to rely on whether test_inherit or test_alias
#comes first, so I'll test for the results of both orders.
assert ["ca","cab"].include?(B.new.foo)
end

#I'm not really sure whether this is the appropriate behavior
#or whether throwing an exception is more appropriate.
def test_neither
assert_nothing_raised do
eval <<-"end;"
class C
override:)foo){|old| "d" }
end
end;
end

assert_equal C.new.foo,"d"
end
end
end

One more unit test that this passes. (This one seems to go to the heart of
the issue you guys are having.)

def test_longchain
assert_nothing_raised do
eval 'class D; def foo; "a"; end; end'
end

assert_equal "a", D.new.foo

10.times do |n|
assert_nothing_raised do
eval 'class D; override :foo do |old| old.call+"a"; end; end'
end

assert_equal "a"*(n+2), D.new.foo
end

end
 
A

ara.t.howard

I have this, it takes a different approach though:

module Patchable
module ClassMethods
def patch(method_name = nil, &new_body)
if method_name
method_name = method_name.to_sym
pre_patched_versions[method_name] = instance_method(method_name)
define_method(method_name, &new_body)
else
klass = Class.new
imeths = klass.instance_methods
klass.class_eval(&new_body)
new_meths = klass.instance_methods - imeths
new_meths.each do |m|
pre_patched_versions[m.to_sym] = instance_method(m)
end
class_eval(&new_body)
end
self
end

def pre_patched_versions
@pre_patched_versions ||= {}
end
end

def hyper(*args, &block)
meth_name = caller[0][/`([^']+)'/, 1].to_sym
self.class.pre_patched_versions[meth_name].bind(self).call(*args, &block)
end

def self.included(other)
other.extend(ClassMethods)
end
end

class C
include Patchable

def m
'a'
end

p new.m
patch do
def m
hyper + 'b'
end
end

p new.m

patch do
def m
hyper + 'c'
end
end

#p new.m doesn't work, infinite recursion
end

Darn. You seem to have made me discover a bug in my impl. It doesn't work for
more than one level of patching per method. Well maybe someone will give me a
patch too ;)


heh. i think you may be hitting the same thing i was. here's some food for
thought:


this works:

harp:~ > cat a.rb
class C
include Module.new{ def m() 'a' end }
include Module.new{ def m() super + 'b' end }
include Module.new{ def m() super + 'c' end }
end

p C.new.m


harp:~ > ruby a.rb
"abc"


while this does not:

harp:~ > cat a.rb
class C
def m() '' end

include Module.new{ def m() super + 'a' end }
include Module.new{ def m() super + 'b' end }
include Module.new{ def m() super + 'c' end }
end

p C.new.m


harp:~ > ruby a.rb
""

even more distilled:

harp:~ > cat a.rb
class C
def m() '' end

include Module.new{ def m() 'a' end }
end

p C.new.m


harp:~ > ruby a.rb
""


i find that behviour quite suprising... in fact, it seems like a bug, but i'm
probably wrong... thoughts?

cheers.


-a
 
A

ara.t.howard

It would be nice to have this built in to the language -- a way to blur the
difference between overriding an inherited method and overriding a method
defined on the current class, so that `super' would call whatever the
previous version of the was.

Try this idea on for size. I wrote it a few days ago. Look at the unit
tests at the end to see how it's intended to be used. I think a little
syntactic sugar would be nice, but it gets the job done.

the show stopper for me is the fact that define_method cannot define methods
that take blocks - so your approach loses them during the alias process.

i remember that define_method will soon have the ability to define methods
that take blocks do you know if that's in 1.8.5 or the 1.9 branch?

cheers.


-a
 
J

Jason Nordwick

The best alias_method methods would be one that didn't exist.

(1) It encourages breaking of naming conventions since at times two functions get defined, one with the convension and one without, (see hash.merge! and hash.update), and I really liked the convention where mutatations have bangs at the end.

(2) This one function double the complexity of learning to read ruby. Now I don't just have to know what hash.update does, but also that hash.merge! is the same thing. And when individual developers start adding aliases to buildins things get ever worse. Array.lower isn't necessary when Array.first and Array[0] are both as short or shorter and just as descriptive.

Alaises increase complexity and really are one of Ruby's biggest dislikes for me.

-j
 
A

ara.t.howard

The best alias_method methods would be one that didn't exist.
(1) It encourages breaking of naming conventions since at times two functions
get defined, one with the convension and one without, (see hash.merge! and
hash.update), and I really liked the convention where mutatations have bangs
at the end.

(2) This one function double the complexity of learning to read ruby. Now I
don't just have to know what hash.update does, but also that hash.merge! is
the same thing. And when individual developers start adding aliases to
buildins things get ever worse. Array.lower isn't necessary when Array.first
and Array[0] are both as short or shorter and just as descriptive.

Alaises increase complexity and really are one of Ruby's biggest dislikes for
me.

the primary reason to use aliases is not to simply have another handle on
names - but override or wrap a method for instance.

require 'sync'

class Module
def ex m
module_eval{
include Sync_m
alias_method "__#{ m }__", "#{ m }"
}
module_eval <<-code
def #{ m } *a, &b
synchronize{ __#{ m }__ *a, &b }
end
code
end
end


class C
def foobar
42
end

ex :foobar
end


if you do much metaprogramming and/or dsl contruction you find, in a matter of
seconds, that alias_method is essential. i think most people find the
declarative style of class methods as syntax (think 'attr',
'has_and_belongs_to_many', etc) to be one of ruby's best features.
unfortunately it oftern hinges on alias_method which has issues if a cycle is
setup.

regards.

-a
 
K

Ken Bloom

I have this, it takes a different approach though:
[Code snipped. You'll have to look above if you want to know how the
broken code works]
Darn. You seem to have made me discover a bug in my impl. It doesn't
work for more than one level of patching per method. Well maybe someone
will give me a patch too ;)

caller knows a function only by the name you call it.

hence

class A
def hyper
p caller
end

def a
hyper
end
alias_method :b,:a
end

a=A.new
puts "Calling by a"
a.a
puts "Calling by b"
a.b

gives:
Calling by a
["(irb):7:in `a'", "(irb):14:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"]
Calling by b
["(irb):7:in `b'", "(irb):16:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"]

In your code, we need to do several things:
* rename and call them by their new names to prevent infinite recursion
* keep track of pre_patched_versions correctly when their name changes

So here's my fix for your code, to incorporate these ideas.
I think that if anyone uses straight up alias_method on methods that we
are patching, things will break, so it's probably a good idea to figure out
how to fix that too.


module Patchable
module ClassMethods
def unused_alias
newalias=nil
while newalias==nil or instance_methods.include?(newalias)
newalias=:"__kenoverride__#{rand(10**20)}__"
end
newalias
end

def patch(method_name = nil, &new_body)
if method_name
method_name = method_name.to_sym
w=unused_alias
alias_method w,method_name
remove_method method_name
pre_patched_versions[w]=pre_patched_versions[method_name.to_sym]\
if pre_patched_versions.include?(method_name.to_sym)
pre_patched_versions[method_name.to_sym] = w
define_method(method_name, &new_body)
else
klass = Class.new
imeths = klass.instance_methods
klass.class_eval(&new_body)
new_meths = klass.instance_methods - imeths
new_meths.each do |m|
w=unused_alias
alias_method w,m
remove_method m
pre_patched_versions[w]=pre_patched_versions[m.to_sym]\
if pre_patched_versions.include?(m.to_sym)
pre_patched_versions[m.to_sym] = w
end
class_eval(&new_body)
end
self
end

def pre_patched_versions
@pre_patched_versions ||= {}
end
end

def hyper(*args, &block)
meth_name = caller[0][/`([^']+)'/, 1].to_sym
tocall=self.class.pre_patched_versions[meth_name]
send(tocall,*args, &block)
end

def self.included(other)
other.extend(ClassMethods)
end
end


class C
include Patchable

def m
'a'
end

p new.m
patch do
def m
raise StandardError if caller.length>10
hyper + 'b'
end
end

p new.m

patch do
def m
raise StandardError if caller.length>10
hyper + 'c'
end
end

p new.m

#you didn't have a test case for this, but I added it

patch :m do hyper+'d' end

p new.m

end
 
K

Ken Bloom

i've been wanting a better alias_method for quite some time.
essentially i'd
like a way out of the trap where executing

alias_method '__fubar__', 'fubar'

goes haywire when __fubar__ already exists. we've all seen it
happen before.

anyhow. the interface it'd like would be


class C
def m() 'a' end

push_method 'm'

def m() super + 'b' end
end

p C.new.m #=> 'ab'


what i've got is quite close, but no cigar. it has a fundemental
problem with
the way ruby scopes super which i'm too tired atttm to figure out.
i'm hoping
i can go to bed and wake up to a nice patch ;-) here's what i've got:


harp:~ > cat a.rb
class Module
def push_method m
this = self

include Module.new{
@m = this.instance_method m

this.module_eval{ remove_method m }

module_eval <<-code
def #{ m }(*a, &b)
um = ObjectSpace._id2ref #{ @m.object_id }
um.bind(self).call *a, &b
end
code
}
end
end

class C
def m
'a'
end
p new.m #=> 'a'


push_method 'm'


def m
super + 'b'
end
p new.m #=> 'ab'


push_method 'm'


def m
super + 'c'
end
p new.m #=> 'abc'
end



harp :~ > ruby a.rb
"a"
"ab"
a.rb:31:in `m': stack level too deep (SystemStackError)
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:31:in `m'
... 2343 levels...
from a.rb:31:in `m'
from (eval):3:in `m'
from a.rb:40:in `m'
from a.rb:42


have at it - i'll be back in 8 hrs. ;-)

I have this, it takes a different approach though:

module Patchable
module ClassMethods
def patch(method_name = nil, &new_body)
if method_name
method_name = method_name.to_sym
pre_patched_versions[method_name] = instance_method
(method_name)
define_method(method_name, &new_body)
else
klass = Class.new
imeths = klass.instance_methods
klass.class_eval(&new_body)
new_meths = klass.instance_methods - imeths
new_meths.each do |m|
pre_patched_versions[m.to_sym] = instance_method(m)
end
class_eval(&new_body)
end
self
end

def pre_patched_versions
@pre_patched_versions ||= {}
end
end

def hyper(*args, &block)
meth_name = caller[0][/`([^']+)'/, 1].to_sym
self.class.pre_patched_versions[meth_name].bind(self).call
(*args, &block)
end

def self.included(other)
other.extend(ClassMethods)
end
end

class C
include Patchable

def m
'a'
end

p new.m
patch do
def m
hyper + 'b'
end
end

p new.m

patch do
def m
hyper + 'c'
end
end

#p new.m doesn't work, infinite recursion
end

Darn. You seem to have made me discover a bug in my impl. It doesn't
work for more than one level of patching per method. Well maybe
someone will give me a patch too ;)

caller knows a function only by the name you call it.

hence

class A
def hyper
p caller
end

def a
hyper
end
alias_method :b,:a
end

a=A.new
puts "Calling by a"
a.a
puts "Calling by b"
a.b

gives:
Calling by a
["(irb):7:in `a'", "(irb):14:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"]
Calling by b
["(irb):7:in `b'", "(irb):16:in `irb_binding'", "/usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", ":0"]

In your code, we need to do several things:
* rename and call them by their new names to prevent infinite recursion
* keep track of pre_patched_versions correctly when their name changes

So here's my fix for your code, to incorporate these ideas.
I think that if anyone uses straight up alias_method on methods that we
are patching, things will break, so it's probably a good idea to figure out
how to fix that too.


module Patchable
module ClassMethods
def unused_alias
newalias=nil
while newalias==nil or instance_methods.include?(newalias)
newalias=:"__kenoverride__#{rand(10**20)}__"
end
newalias
end

def patch(method_name = nil, &new_body)
if method_name
method_name = method_name.to_sym
w=unused_alias
alias_method w,method_name
remove_method method_name
pre_patched_versions[w]=pre_patched_versions[method_name.to_sym]\
if pre_patched_versions.include?(method_name.to_sym)
pre_patched_versions[method_name.to_sym] = w
define_method(method_name, &new_body)
else
klass = Class.new
imeths = klass.instance_methods
klass.class_eval(&new_body)
new_meths = klass.instance_methods - imeths
new_meths.each do |m|
w=unused_alias
alias_method w,m
remove_method m
pre_patched_versions[w]=pre_patched_versions[m.to_sym]\
if pre_patched_versions.include?(m.to_sym)
pre_patched_versions[m.to_sym] = w
end
class_eval(&new_body)
end
self
end

def pre_patched_versions
@pre_patched_versions ||= {}
end
end

def hyper(*args, &block)
meth_name = caller[0][/`([^']+)'/, 1].to_sym
tocall=self.class.pre_patched_versions[meth_name]
send(tocall,*args, &block)
end

def self.included(other)
other.extend(ClassMethods)
end
end


class C
include Patchable

def m
'a'
end

p new.m
patch do
def m
raise StandardError if caller.length>10
hyper + 'b'
end
end

p new.m

patch do
def m
raise StandardError if caller.length>10
hyper + 'c'
end
end

p new.m

#you didn't have a test case for this, but I added it

patch :m do hyper+'d' end

p new.m

end
 
K

Ken Bloom

The best alias_method methods would be one that didn't exist.
(1) It encourages breaking of naming conventions since at times two functions
get defined, one with the convension and one without, (see hash.merge! and
hash.update), and I really liked the convention where mutatations have bangs
at the end.

(2) This one function double the complexity of learning to read ruby. Now I
don't just have to know what hash.update does, but also that hash.merge! is
the same thing. And when individual developers start adding aliases to
buildins things get ever worse. Array.lower isn't necessary when Array.first
and Array[0] are both as short or shorter and just as descriptive.

Alaises increase complexity and really are one of Ruby's biggest dislikes for
me.

the primary reason to use aliases is not to simply have another handle on
names - but override or wrap a method for instance.

require 'sync'

class Module
def ex m
module_eval{
include Sync_m
alias_method "__#{ m }__", "#{ m }"
}
module_eval <<-code
def #{ m } *a, &b
synchronize{ __#{ m }__ *a, &b }
end
code
end
end


class C
def foobar
42
end

ex :foobar
end


if you do much metaprogramming and/or dsl contruction you find, in a matter of
seconds, that alias_method is essential. i think most people find the
declarative style of class methods as syntax (think 'attr',
'has_and_belongs_to_many', etc) to be one of ruby's best features.
unfortunately it oftern hinges on alias_method which has issues if a cycle is
setup.

The solution to this problem will actually let alias_method fall into
disuse faster, since we don't need to use alias_method directly to wrap or
override an old method anymore.

--Ken
 
T

Trans

heh. i think you may be hitting the same thing i was. here's some food for
thought:


this works:

harp:~ > cat a.rb
class C
include Module.new{ def m() 'a' end }
include Module.new{ def m() super + 'b' end }
include Module.new{ def m() super + 'c' end }
end

p C.new.m


harp:~ > ruby a.rb
"abc"


while this does not:

harp:~ > cat a.rb
class C
def m() '' end

include Module.new{ def m() super + 'a' end }
include Module.new{ def m() super + 'b' end }
include Module.new{ def m() super + 'c' end }
end

p C.new.m


harp:~ > ruby a.rb
""

even more distilled:

harp:~ > cat a.rb
class C
def m() '' end

include Module.new{ def m() 'a' end }
end

p C.new.m


harp:~ > ruby a.rb
""


i find that behviour quite suprising... in fact, it seems like a bug, but i'm
probably wrong... thoughts?

Not at all becasue inclusion adds to the inheritance chain, it doesn't
inject methods. You can of course define an #inject method if you like.
But it's still difficult to do the orginial intent of this thread b/c
of how #super works.

Personally I think it would cool if you could redefine a method calling
on the previous definition just as one can redefine a variable calling
on it's previous definition. Eg.

a = 1
a = a + 1

def a ; 1 ; end
def a ; a + 1 ; end

This would eliminate the need for #super except in cases of recursion,
in which case a special notation is also needed for the definition. Eg.
def_rec a, or something. But that's cool b/c it would be safer.

In anycase, alternate solutions to the alias issue include
#wrap_method, #alias_chain, Matz' :before and :after system (semi-aop)
and cuts (aop).

T.
 
M

Mauricio Fernandez

the show stopper for me is the fact that define_method cannot define methods
that take blocks - so your approach loses them during the alias process.

i remember that define_method will soon have the ability to define methods
that take blocks do you know if that's in 1.8.5 or the 1.9 branch?

It's been in 1.9 for quite a long time now.
 
A

ara.t.howard

It's been in 1.9 for quite a long time now.

i thought so. still, i'm unclear whether or not it'll make the task any
easier. anyhow... i've made progress. here's the latest:

btw - i like the look of call_stack - good work!

the latest, it gets over the redef issue:


harp:~ > ruby a.rb
"a"
"ab"
"abc"



harp:~ > cat a.rb
class Module
def child this = self
@child ||= self.class.new
@child.module_eval{ include this}
@child
end

def has_child
defined? @child and @child
end

def override m, &b
this = self

m = Module.new{
@m = this.instance_method m
this.module_eval{ remove_method m rescue nil }

module_eval <<-code
def #{ m }(*a, &b)
um = ObjectSpace._id2ref #{ @m.object_id }
um.bind(self).call *a, &b
end
code

child.module_eval &b if b # put super into right scope!
}

include(m.has_child ? m.child : m)
end
end

class C
def m() 'a' end
p new.m #=> 'a'

override('m'){ def m() super + 'b' end }
p new.m #=> 'ab'

override('m'){ def m() super + 'c' end }
p new.m #=> 'abc'
end



if course, the cool thing is that it even works for blocks:


harp:~ > cat a.rb
require 'override'

class C
def m(&b) b.call end

override:)m){ def m(&b) super + 2 end }
end

p C.new.m{ 40 }


harp:~ > ruby a.rb
42



now - making it undoable, abstracting, etc. is a lot more work...


-a
 
K

Ken Bloom

Not at all becasue inclusion adds to the inheritance chain, it doesn't
inject methods. You can of course define an #inject method if you like.
But it's still difficult to do the orginial intent of this thread b/c
of how #super works.

Personally I think it would cool if you could redefine a method calling
on the previous definition just as one can redefine a variable calling
on it's previous definition. Eg.

a = 1
a = a + 1

def a ; 1 ; end
def a ; a + 1 ; end

This would eliminate the need for #super except in cases of recursion,
in which case a special notation is also needed for the definition. Eg.
def_rec a, or something. But that's cool b/c it would be safer.

In anycase, alternate solutions to the alias issue include
#wrap_method,

see http://www.erikveen.dds.nl/monitorfunctions/index.html#implementation
#alias_chain, Matz' :before and :after system (semi-aop) and cuts (aop).

Where is there information about these?

--Ken
 
S

Sylvain Joyeux

a = 1
a = a + 1

def a ; 1 ; end
def a ; a + 1 ; end

This would eliminate the need for #super except in cases of recursion,
in which case a special notation is also needed for the definition. Eg.
def_rec a, or something. But that's cool b/c it would be safer.

I don't see how you write "recursive methods which call super".
 
E

evanwebb

I encountered this same problem a while ago and also began to come up
with a solution.

One of my use cases was the now-common override of Kernel#require to
provide some new behavior for requiring files.

I came up with Maskable:
http://hoshi.fallingsnow.net/svn/maskable/maskable.rb

There is some sample code at the bottom of that file. You use it by
putting your new implementations of methods in a Module and then
calling Module#add_mask(ModuleWithNewImpl).

I found it nice because it forces you to organizes your "mask" methods
into the module to make them easier to understand.

To call add_mask on the Module, you have to extend the module with
Method::Maskable. But of course if you want to be able to mask any
method in any class/module, you can just do
Module.extend(Method::Maskable).

For a mask to call the next method in the chain, It just calls
Method.rest.

Any thoughts? It's a little nasty, but works pretty well last I used
it.
 
T

TRANS

I don't see how you write "recursive methods which call super".

Like is Haskell you'd have to explictily state it's recursive. So for
instance I'm suggesting:

class A
def x; "x"; end
end

class B < A
def x; x.upcase; end
end

Ordinarily B#x would recurse (and be an infinite loop). But not so in
my suggestion. So the above would work and rather you'd have to tell
it esspecially if recursion were desired, something like:

class A
def x(n); n - 1 ; end
end

class B < A
def_rec x(n)
x == 1 ? 1 : n + x(x(n)) # err... x(super(n))
end
end

Now in this case how do you call A#x from B#x if you need to? We would
still need to have #super as shown in the comment.

Hmm... better yet, you could do without super altogther and also not
need the special def_rec if we had a special method that meant "this
method", maybe #this. So:

class B < A
def x(n)
x == 1 ? 1 : n + this(x(n))
end
end

Would work for the recursive case.

T.
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top