Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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