Tricks with method as superclass

C

Clifford Heath

I'm intrigued by the way Camping uses a method invocation to
serve as a superclass, and while exploring, possibly found a
bug? At any rate, some behaviour I can't explain... but first
a simple example of what Camping does:

class Foo
def initialize(*args)
puts "Initializing Foo"
end
end
=> nil

def Foo(*args)
puts args.inspect
Class.new(Foo) # Interesting that this works...
end
=> nil

class Bar < Foo("Hi there!")
end
["Hi there!"]
=> nil

Bar.new
Initializing Foo
=> #<Bar:0x2f84358>

Ok, all's well, and it's basically what Camping does.
Now what if I want to pass a *block* to the method
that will return my superclass?

def Foo(*args, &block)
puts args.inspect
block.call if block
Class.new(Foo)
end
=> nil

class Bar < Foo("Hi There!") {
puts "Surprise!"
}
end
["Hi there!"]
Surprise!
=> nil

Cool, that worked well. Now I want to keep the args and block passed
that was passed to the method, so I tried the following, which makes
an anonymous subclass of Foo, and Bar is a subclass of that:

class Foo
def initialize(*args)
puts "Initializing Foo"
end
end
=> nil

def Foo(*args, &block)
puts args.inspect
block.call if block
Class.new(Foo).instance_eval <<-END
def initialize
# I'm going to do something with block and args here
puts "Double surprise!"
end
self
END
end
=> nil

class Bar < Foo("Hi there!") {
puts "Surprise!"
}
end
["Hi there!"]
Surprise!
=> nil

Bar.new
Initializing Foo
=> #<Bar:0x2f776b0>

What happened to my Double surprise? It seems that instance_eval hasn't
caused Bar.new to use my extra layer of initialize... The instance of
Bar has the anonymous superclass in it's hierarchy, but the initialize()
doesn't appear in the call stack. What gives here?

Clifford Heath.
 
A

ara.t.howard

I'm intrigued by the way Camping uses a method invocation to
serve as a superclass, and while exploring, possibly found a
bug? At any rate, some behaviour I can't explain... but first
a simple example of what Camping does:

class Foo
def initialize(*args)
puts "Initializing Foo"
end
end
=> nil

def Foo(*args)
puts args.inspect
Class.new(Foo) # Interesting that this works...
end
=> nil

class Bar < Foo("Hi there!")
end
["Hi there!"]
=> nil

Bar.new
Initializing Foo
=> #<Bar:0x2f84358>

Ok, all's well, and it's basically what Camping does.
Now what if I want to pass a *block* to the method
that will return my superclass?

def Foo(*args, &block)
puts args.inspect
block.call if block
Class.new(Foo)
end
=> nil

class Bar < Foo("Hi There!") {
puts "Surprise!"
}
end
["Hi there!"]
Surprise!
=> nil

Cool, that worked well. Now I want to keep the args and block passed
that was passed to the method, so I tried the following, which makes
an anonymous subclass of Foo, and Bar is a subclass of that:

class Foo
def initialize(*args)
puts "Initializing Foo"
end
end
=> nil

def Foo(*args, &block)
puts args.inspect
block.call if block
Class.new(Foo).instance_eval <<-END
def initialize
# I'm going to do something with block and args here
puts "Double surprise!"
end
self
END
end
=> nil

class Bar < Foo("Hi there!") {
puts "Surprise!"
}
end
["Hi there!"]
Surprise!
=> nil

Bar.new
Initializing Foo
=> #<Bar:0x2f776b0>

What happened to my Double surprise? It seems that instance_eval hasn't
caused Bar.new to use my extra layer of initialize... The instance of
Bar has the anonymous superclass in it's hierarchy, but the initialize()
doesn't appear in the call stack. What gives here?


harp:~ > cat a.rb
class Foo
def initialize(*args)
puts "Initializing Foo"
end
end

def Foo(*args, &block)
puts args.inspect
block.call if block
# Class.new(Foo).instance_eval <<-END
Class.new(Foo).module_eval <<-END
def initialize
# I'm going to do something with block and args here
puts "Double surprise!"
end
self
END
end

class Bar < Foo("Hi there!") {
puts "Surprise!"
}
end

Bar.new


harp:~ > ruby a.rb
["Hi there!"]
Surprise!
Double surprise!


regards.

-a
 
M

Max Muermann

Cool, that worked well. Now I want to keep the args and block passed
that was passed to the method, so I tried the following, which makes
an anonymous subclass of Foo, and Bar is a subclass of that:

class Foo
def initialize(*args)
puts "Initializing Foo"
end
end
=> nil

def Foo(*args, &block)
puts args.inspect
block.call if block
Class.new(Foo).instance_eval <<-END
def initialize
# I'm going to do something with block and args here
puts "Double surprise!"
end
self
END

This is really cool. Regarding the double surprise problem, class_eval
instead of instance_eval (and a call to super) does the trick:

Class.new(Foo).class_eval <<-END
def initialize
super
# I'm going to do something with block and args here
puts "Double surprise!"
end
self
END

=> ["Hi there!"]
Surprise!
Initializing Foo
Double surprise!

--max
 
C

Clifford Heath

# Class.new(Foo).instance_eval <<-END
Class.new(Foo).module_eval <<-END

Thanks Ara - super-quick response too! Caught out by the singleton,
which I didn't expect to find on a Class instance. Here's a complete
example that shows most of what's possible, assuming the attachment
comes through.

Clifford Heath.

# Start with a real base class
class Base
def initialize(*args, &block)
# puts "Base::initialize #{args.inspect} &#{block.inspect}"
print("Base::")
block.call(*args) if block
end

# Return a new anonymous class derived from Base, with class variables and initialize
def self.augment(*args, &block)
Class.new(self).class_eval {
@@_base_args = args
@@_base_block = block
# puts "new class is #{self.object_id} #{args.inspect} &#{block.inspect}"

def initialize(*args, &block)
# puts "Constructing instance with base #{@@_base_args.inspect} &#{@@_base_block.inspect}, instance #{args.inspect} &#{block.inspect}"
@@_base_block.call(*@@_base_args) if @@_base_block
super(*args, &block)
end

self
}
end
end

# Here's the method used to create the custom base class:
def Base(*args, &block)
puts "Defining funky magic with #{args.inspect} &#{block.inspect}"

Base.augment(*args, &block)
end

# A subclass with neither args nor block:
class Sub0 < Base
end
Sub0.new { puts "No augmented class, use Base alone" }

# A subclass with just args:
class Sub1 < Base("arg1")
end
Sub1.new("Thinking...") { |*args| puts "#{args.inspect}: Ok" }

# A subclass with just a block:
class Sub2 < Base { print "Hi" }
end
Sub2.new { puts " there!" }

# A subclass with args and a block:
class Sub3 < Base("arg1", "arg2") { |*args|
puts "#{args.inspect}: getting things all ready for you..."
}

def initialize(*args, &block)
super(*args, &block)

puts "#{args.inspect}: fun for all the family!"
end
end

Sub3.new("sub1", "sub2") { |*args|
puts "#{args.inspect}: still working"
}
 
C

Clifford Heath

Max said:
This is really cool.

Thanks, I agree. Ara beat you to it, but I thought I'd
post a simpler example too. The cute thing about this is
that you can use method_missing magic to make the attached
block use a different DSL than the class body.

$ cat superclass-method-simple.rb
class Foo
def initialize(*args)
puts "Initializing a Foo"
end
end

def Foo(*args, &block)
Class.new(Foo).class_eval <<-END
@@_args = args
@@_block = block
def initialize
super
puts @@_args.inspect
@@_block.call if @@_block
end
self
END
end

class Bar < Foo("Hi there!") {
puts "Surprise!"
}
end

puts "All ready, here goes:"
Bar.new

$ ruby superclass-method-simple.rb
All ready, here goes:
Initializing a Foo
["Hi there!"]
Surprise!
$

Cute, huh? :)
 

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

Forum statistics

Threads
473,772
Messages
2,569,591
Members
45,100
Latest member
MelodeeFaj
Top