Metaprogramming Discussion

Download Report

Transcript Metaprogramming Discussion

Metaprogramming Discussion
• Please make this interesting…. Interrupt
me!
• This should be a discussion
• Metaprogramming should become just
‘programming’ after a while… It’s
integrated into the Ruby toolset.
Boston’s Ruby User Group
5/9/2006
Daniel Eklund (that’s me)
Purpose of these Slides
1. Define/discuss metaprogramming and
how it enables:
1. Embeddable DSLs in Ruby
2. Different programming paradigms in Ruby
•
•
Survey of Programming Techniques for
Ruby Metaprogramming
Discuss Why/How Ruby got to be so good
at accomplishing the above
Part 1
Define/discuss metaprogramming
Definition of Metaprogramming
My definition is correct! Yours is not!
Kinda like defining Object Oriented
(a doomed proposition)
–
–
–
–
Class based? (not Javascript or Self)
Information Hiding? (not C++, Java)
Inheritance? (violates encapsulation according to some)
Methods Belong to Class/Object? (not CLOS)
You get a feel for it. Like Jazz.
Definition of Metaprogramming
• Rule of Thumb 1:
– Code = Data
• Rule of Thumb 2:
– Is your program writing ‘code’ for you?
• Rule of Thumb 3:
– Anytime you are using eval, instance_eval,
define_method, or module_eval
attr_reader :name, :ssn, :salary
What Metaprogramming Allows
• Embeddable DSLs (contrast with external DSLs like Ant, or XDoclet)
–
–
–
–
Rails (DSL for O/R mapping and web-programming)
Rake (DSL for system definition and building)
XML Builder (DSL for arbitrary XML structure)
Attr syntaxes ( sort of a mini DSL for beans)
• Embeddable Programming Paradigms
– Object Oriented (default)
– Procedural (we get this free, that is, this does not come from metaprogramming)
– Declarative
• Rules-based
• Logical
• Constraint
DSLs and Programming
Paradigms
• Embeddable DSLs and different
programming paradigms are not the same as
metaprogramming
• But they all are dependent on each other,
and made easier/possible from each other
• Just like ‘metaprogramming’, ‘DSL’ and
‘paradigm’ can have many definitions… but
we don’t want to open that can of worms
Definition of Metaprogramming
• Border Case
– Defining your own block handling methods
– A block is a delayed execution, so it could be
argued that it’s used in piecing together new
run-time functions (i.e. higher-order functions)
– Won’t press the point
Purpose of these Slides
1. Define/discuss metaprogramming and
how it enables:
1. Embeddable DSLs in Ruby
2. Different programming paradigms in Ruby
•
•
Survey of Programming Techniques for
Ruby Metaprogramming
Discuss Why/How Ruby got to be so good
at accomplishing the above
Part 2
Survey of Programming
Techniques for Ruby
Metaprogramming
Metaprogramming Techniques
a survey of…
• How/When do we invoke the
metaprogramming eval methods?
• What are the most common ways of doing
this?
1.
2.
3.
4.
Behind ‘attr like’ methods
Behind hooks
Behind method_missing
The Miscellaneous Catch All Category
Attr-like Syntaxes
• The fact that methods like attr_reader,
attr_accessor are sometimes called
syntaxes says something
• Psychologically/Visually these methods on
the class object itself remind us of other
programming languages syntaxes
Attr-like Syntaxes
Visual Similarity
public class Employee{
public static int debug;
private transient String secretName;
private int ssn;
}
class Employee
< ActiveRecord::Base
attr_accessor :ssn;
has_many :problems;
end
BTW
• Would we still feel comfortable writing :
class Employee
attr_accessor :ssn, :name, :salary
end
like this?
(just a question to mull)
class Employee
attr_accessor(:ssn,:name,:salary);
end
Typical Scenario for Attr-like
Methods
class Employee
attr_eater :ssn, :name, :salary
end
module EmployeeLikeFlavors
# for later mixing
attr_eater :ssn, :name, :salary
end
• If we want to create our own ‘syntax’ like
the one show above …
Typical Scenario for Attr-like
Methods
class Module
def attr_eater(*syms)
syms.each do |method_name|
new_method =
%{def eat_#{method_name.to_s}(arg)
puts “Me like cookie”
@#{method_name.to_s} = arg
end}
module_eval
new_method
# the magic
end
end
end
But do we want this method to be available everywhere???
Typical Scenario for Attr-like
Methods
class Module
def attr_eater(*syms)
# implementation elided to save vertical space
end
end
By defining our new method inside Module we
make this method available to all modules and
classes throughout the system
Typical Scenario for Attr-like
Methods
class Class
def attr_eater(*syms)
# implementation elided to save vertical space
end
end
By changing this to Class, the attr_eater is
now accessible in all classes throughout the
system, but not any modules.
Typical Scenario for Attr-like
Methods
class Employee
def self.attr_eater(*syms)
# implementation elided to save vertical space
end
end
By changing the class to Employee and
changing the attr_eater to a classaccessible method, this method is available
only to the Employee class
Typical Scenario for Attr-like
Methods
class
Too specific
Employee
def
self.attr_eater(*syms)
# implementation elided to save vertical space
class
end
Module
def attr_eater(*syms)
end
# implementation elided to save vertical space
class
end
Class
def attr_eater(*syms)
end
# implementation elided to save vertical space
end
end
less general, but in
an arbitrary way
Way too general
(unless that’s what you
want)
Wouldn’t it be nice if we could mix in new
syntax as we needed it?
Typical Scenario for Attr-like
Methods
module Edible
def attr_eater(*syms)
# implementation elided to save vertical space
end
end
We change class to module, because we are
providing this functionality to anybody to mix
in as they choose. Name the module as you
want.
Typical Scenario for Attr-like
Methods
class Employee
extend Edible
attr_eater :ssn, :name, :salary
end
• Now all we do is mix it in using extend (not
include) …
• See forwardable.rb in the Ruby distribution
Metaprogramming Techniques
1.
2.
3.
4.
Behind ‘attr like’ methods
Behind hooks
Behind method_missing
The Miscellaneous Catch All Category
What’s a Hook?
• A method called when something else happens…
sometimes called callback methods
• Ruby provides certain event hooks
–
–
–
–
–
–
–
–
–
method_added
extend_object (pre extension)
extended (post extension)
included
method_defined (not method_defined?)
method_undefined
singleton_method_added
inherited
method_missing (which will get its own section)
Creating your own hook
• Create your own hook by overriding an
existing method.
• So, just because there was no hook provided
for class creation nothing stops us from
doing it on our own
Creating your own hook
class Class
alias_method :old_new, :new
def new(*args)
result = old_new(*args)
result.timestamp = Time.now
return result
end
class Object
end
def timestamp
return @timestamp
end
def
timestamp=(aTime)
@timestamp = aTime
end
end
shamelessly stolen from Pragmatic Ruby
Metaprogram behind the hook
(an example)
• How about memoization? (snazzy word for caching)
This code found at:
http://raa.ruby-lang.org/project/memoize/
http://blog.grayproductions.net/articles/2006/01/20/caching-and-memoization (is
slightly better, IMHO)
Metaprogram behind the hook
(not quite yet…)
• To use, you do:
require "memoize"
include Memoize
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
fib(100)
memoize(:fib)
fib(100)
No metaprogramming with hooks has yet occurred
Metaprogram behind the hook
(if you so desire)
• Wouldn’t it now be nice if the memoization
occurred automatically (i.e. with the definition of
a method)
• Be careful of the infinite loops
• There has GOT to be a better example that doesn’t
involve metacircular confusion
• Exercise left to audience
• And because I couldn’t quite get it working
Metaprogram behind the hook
(homework #1)
class MathStuff
extend MemoizeClass
def fibonacci
# elide
end
end
• The above is what it should look like
• All methods in this class should be memoized
Metaprogram behind the hook
(homework #2)
class MathStuff
extend MemoizeWithPattern(/^fib/)
def fibonacci
# elide
end
end
Tricky!
This is actually a
method
• Exercise left to audience
• Memoize methods with a particular pattern
Good Example of Using Hooks
http://wiki.rubyonrails.com/rails/pages/Extend
ingActiveRecordExample
Has a good example of the
append_features hook… It does not
quite use metaprogramming directly, but
since Rails is so full of metaprogramming
goodies, you can forgive me
Metaprogramming Techniques
1.
2.
3.
4.
Behind ‘attr like’ methods
Behind hooks
Behind method_missing
The Miscellaneous Catch All Category
onwards!
Method Missing!
Panic!
• Don’t Panic
• Ruby provides a hook/callback method for
the unlikely (or planned for) event of a
method not existing on the object
• It’s not just late binding… it’s SUPER late
binding
Method Missing!
Everyone’s Favorite Example
class Roman
def initialize()
@data = [["M" , 1000],["CM" , 900],["D" , 500],["CD" , 400],
["C" , 100],["XC" , 90],["L" , 50],["XL" , 40],
["X" , 10],["IX" , 9],["V" , 5],["IV" , 4], ["I" , 1]]
end
def toArabic(rom)
rom.upcase!
reply = 0
for key, value in @data
while rom.index(key) == 0s
reply += value
rom.slice!(key)
end
end
reply
end
def method_missing(methId)
str = methId.id2name
toArabic(str)
end
end
Method Missing: Roman
Numeral Example
• This is in of itself could be called
metaprogramming, since the late dispatch
essentially responds to messages on the fly (the
same method toArabic() is called though)
• It absolutely could define (with eval) a method for
the new message, to provide a shortcut
me = Roman.new
me.x
# returns 10
me.viii
# returns 8
Method Missing
another example XML Builder
• http://www.xml.com/pub/a/2006/01/04/creat
ing-xml-with-ruby-and-builder.html
• http://rubyforge.org/projects/builder
Method Missing
XML Builder
x.body {
x.h1 "Hello from Builder"
x.p "A Ruby library that facilitates the programatic generation of
XML."
x.p { |y| y <<"Methods of interest from
<code<Builder::XmlMarkup</code> }
x.ul {
x.li
x.li
x.li
x.li
x.li
}
}
"cdata!"
"comment!"
"declare!"
"instruct!"
"new"
Methods aren’t
known until codingtime.
Would you want to
use a library that
didn’t let you add a
new XML element?
Metaprogramming Techniques
1.
2.
3.
4.
Behind ‘attr like’ methods
Behind hooks
Behind method_missing
The Miscellaneous Catch All Category
finally… an end
The Miscellaneous Catch All
Category (MCAC)
• That expression to the right of the extends
symbol
• ObjectSpace tomfoolery (see Ruby
TestUnit)
• Anything else I might have left out (like
exceptions, etc) – the MCAC for this MCAC
That expression to the right of the
extends symbol
The extends symbol
class Fred < DelegateClass(Flintstone)
def initialize
# ...
super(Flintstone.new(...))
This is the
end
expression I am
# ...
talking about
end
DelegateClass(obj) is a method
invocation that builds a class and methods
on the fly.
This example found in
delegator.rb as part of the Ruby
distribution
Part 3
Why is Ruby so good at embedding
DSLs and at metaprogramming?
Why is it so Good?
A clever brew of:
• Large macroscopic language decisions
• A huge heaping serving of syntactic sugar
Why is it so Good?
• Large macroscopic language decisions
–
–
–
–
–
Blocks
Open class definitions
Modules
Dynamic typing (almost goes without saying)
No syntactic dead-space
Syntactic Dead Space?
public class Employee{
public static int debug;
private int ssn;
public int getSSN(){
return ssn;
}
The Unthinking
Depths *
The Transcend*
code works here
public void setSSN(int ssn){
this.ssn = ssn;
}
}
* See: http://en.wikipedia.org/wiki/A_Fire_Upon_the_Deep
In Java, you need a static block
public class Employee{
public static int debug;
private int ssn;
static {
debug = Configurator.getSetting(“debug.level”);
}
public int getSSN(){
return ssn;
}
public void setSSN(int ssn){
this.ssn = ssn;
The static block
is a bubble of
The Transcend,
i.e. code works
here
}
}
Now our dead space is alive.. Like a zombie
In Ruby, live-space codes you!!
class Employee
extend Edible
attr_eater :ssn, :name, :salary
puts “self is #{ self }”
if ARGV.length > 0
puts “Too many arguments”n
end
end
•It’s like one gigantic implicit static block.
•Both the java static block and the ruby live-space get invoked during
class loading
Why is it so Good?
• Large macroscopic language decisions
• A huge heaping serving of syntactic sugar
–
–
–
–
Optional parentheses
Optional receivers
Hashes and arrays syntax
Method dispatch can implicitly bundle arguments into
an array or a hash
– Strings (one of the common inputs to the eval methodset) are easy to templatize: %{}, here docs, etc
Final Slide:
Rake Example
Optional parentheses, makes the
method call look like a sentence
Optional
Receiver
task :second => :first do
#second's body
Blocks with their do/end
end
look like method defs.
task :first do
#first's body
end
task :name => [:prereq1, :prereq2]
Method dispatch automatically
bundles the above into a new Hash
object with one key/value pair