Successfully reported this slideshow.
Your SlideShare is downloading. ×

Metaprogramming and Folly

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 47 Ad
Advertisement

More Related Content

Similar to Metaprogramming and Folly (20)

Advertisement

Recently uploaded (20)

Metaprogramming and Folly

  1. 1. Metaprogramming and Folly HASEEB QURESHI SOFTWARE ENGINEER @
  2. 2. What is metaprogramming?
  3. 3. Metaprogramming is code that writes code.
  4. 4. You use it all the time. class Player attr_accessor :health end
  5. 5. You use it all the time. class Player def health @health end def health=(new_health) @health = new_health end end
  6. 6. Macros are the simplest form of metaprogramming.
  7. 7. attr_reader alias_method def_delegators
  8. 8. And if we include Rails… belongs_to has_many_through scope before_filter
  9. 9. When they’re a predictable part of our common language, they don’t really seem like metaprogramming. *********** * ********* * * * * * ***** * * * * * * * * * * * * * * * *** * * * * * * * ******* * * * ***********
  10. 10. But when we talk about metaprogramming, we’re usually referring to “magic.”
  11. 11. Let’s talk about magic.
  12. 12. Object#send
  13. 13. :send is the hook into Ruby’s method dispatch.
  14. 14. [1, 2, 3].sort_by { |x| x.hash.hash } [1, 2, 3].send("sort_by") { |x| x.hash.hash } "hello".reverse "hello".send(:reverse)
  15. 15. It also allows you to access private methods. class Player # ... private def top_secret_password "hunter12" end end Player.new.send(:top_secret_password) #=> "hunter12"
  16. 16. So that’s *mostly* useless. But the real power of :send is dynamic dispatch.
  17. 17. Let’s look at this player class. class Player UI_ACTIONS = [:attack, :defend, :retreat] def attack end def defend end def retreat end def other_method end end
  18. 18. Say our player gets a form: <select> <% Player::UI_ACTIONS.each do |action| %> <option value="<%= action %>"> <%= action.capitalize %> </option> <% end %> </select>
  19. 19. Instead of doing this… case params[:action] when 'attack' current_player.attack when 'defend' current_player.defend when 'retreat' current_player.retreat end
  20. 20. if Player::UI_ACTIONS.include?(params[:action]) end current_player.send(params[:action]) We do this.
  21. 21. String#constantize (ActiveSupport only.)
  22. 22. :constantize allows you to turn strings into constants (including classes).
  23. 23. (It’s essentially a special case of :eval.)
  24. 24. Imagine you have some POROs in your app. class Sword < Weapon def self.damage 10 end end class Spear < Weapon def self.damage 6 end end class Dagger < Weapon def self.damage 4 end end class StronglyWordedEmail < Weapon def self.damage 1 end end
  25. 25. This is a common pattern: Player.first.weapon_type.constantize.damage # => 4 Player: { id: 1, hp: 50, weapon_type: 'Dagger', }
  26. 26. Class#define_method
  27. 27. :define_method allows you to create new instance methods at runtime.
  28. 28. class Player CHAINABLE_MOVES = [:slash, :swipe, :poke] def slash 5 end def swipe 3 end def poke 1 end end
  29. 29. class Player CHAINABLE_MOVES.permutation(2).each do |m1, m2| define_method("#{m1}_and_#{m2}") do send(m1) + send(m2) end end end p Player.new.methods - [].methods # => [:slash, :swipe, :poke, :slash_and_swipe, :slash_and_poke, :swipe_and_slash, :swipe_and_poke, :poke_and_slash, :poke_and_swipe] p Player.new.poke_and_slash # => 6
  30. 30. Now this is definitely magical.
  31. 31. But we can go deeper.
  32. 32. Object# method_missing
  33. 33. :method_missing is like a before_filter to NoMethodErrors.
  34. 34. If you call a method that doesn’t exist, :method_missing is first invoked.
  35. 35. class Player def attack puts "Hiya!" end def defend puts "Ouch." end def retreat puts "AHHHHHHHHH" end end Player.new.triple_attack => undefined method `triple_attack' for #<Player:0x007f971b8291a0> (NoMethodError)
  36. 36. class Player def method_missing(m, *args) if m =~ /^triple_(w+)/ && respond_to?($1) 3.times { send($1) } else puts "I can't do that..." end end end Player.new.triple_attack => Hiya! => Hiya! => Hiya! Let’s just add this.
  37. 37. Pretty cool, right?
  38. 38. Actually, this is terrible. Never do this.
  39. 39. Most metaprogramming has no place in a production codebase: method_missing eval instance_eval class_eval instance_variable_set const_set
  40. 40. Let’s talk why.
  41. 41. Pros: • Powerful DSLs - You think you want this, but you probably don’t. • Keeps things DRY! - Be careful. You can go overboard.
  42. 42. Cons: • Greppability? • Greppability. • Greppability… • Greppability!!
  43. 43. class Player def method_missing(m, *args) if m =~ /^triple_(w+)/ && respond_to?($1) 3.times { send($1) } else puts "I can't do that..." end end end > grep triple_attack * Player.rb:21:Player.new.triple_attack > ...wtf
  44. 44. Cons: • It creates a high cognitive load on anyone entering your codebase. • Makes debugging harder, stack traces more mysterious, static analysis harder. • It can easily commit you to the wrong abstractions.
  45. 45. Metaprogramming is magical.
  46. 46. But true magic demands that we all believe in it.
  47. 47. Thanks for listening. You can follow me at @hosseeb

×