Top Banner
Metaprogramming and Folly HASEEB QURESHI SOFTWARE ENGINEER @
47

Metaprogramming and Folly

Apr 12, 2017

Download

Software

Haseeb Qureshi
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Metaprogramming and Folly

Metaprogramming and Folly

HASEEB QURESHI

SOF TWARE ENGINEER @

Page 2: Metaprogramming and Folly

What is metaprogramming?

Page 3: Metaprogramming and Folly

Metaprogramming is code that writes code.

Page 4: Metaprogramming and Folly

You use it all the time.

class Player attr_accessor :healthend

Page 5: Metaprogramming and Folly

You use it all the time.

class Player def health @health end

def health=(new_health) @health = new_health endend

Page 6: Metaprogramming and Folly

Macros are the simplest form of metaprogramming.

Page 7: Metaprogramming and Folly

attr_reader

alias_method

def_delegators

Page 8: Metaprogramming and Folly

And if we include Rails…

belongs_to

has_many_through

scope

before_filter

Page 9: Metaprogramming and Folly

When they’re a predictable part of our common language,

they don’t really seem like metaprogramming.

*********** ********** ** * ** ***** * ** * * * ** * * * * ** * *** * ** * * ** ******* ** ************

Page 10: Metaprogramming and Folly

But when we talk about metaprogramming, we’re

usually referring to “magic.”

Page 11: Metaprogramming and Folly

Let’s talk about magic.

Page 12: Metaprogramming and Folly

Object#send

Page 13: Metaprogramming and Folly

:send is the hook into Ruby’s method dispatch.

Page 14: Metaprogramming and Folly

[1, 2, 3].sort_by { |x| x.hash.hash } [1, 2, 3].send("sort_by") { |x| x.hash.hash }

"hello".reverse

"hello".send(:reverse)

Page 15: Metaprogramming and Folly

It also allows you to access private methods.

class Player # ... private

def top_secret_password "hunter12" endend

Player.new.send(:top_secret_password) #=> "hunter12"

Page 16: Metaprogramming and Folly

So that’s *mostly* useless.

But the real power of :send is dynamic dispatch.

Page 17: Metaprogramming and Folly

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 endend

Page 18: Metaprogramming and Folly

Say our player gets a form:

<select> <% Player::UI_ACTIONS.each do |action| %> <option value="<%= action %>"> <%= action.capitalize %> </option> <% end %></select>

Page 19: Metaprogramming and Folly

Instead of doing this…

case params[:action]when 'attack' current_player.attackwhen 'defend' current_player.defendwhen 'retreat' current_player.retreatend

Page 20: Metaprogramming and Folly

if Player::UI_ACTIONS.include?(params[:action])

endcurrent_player.send(params[:action])

We do this.

Page 21: Metaprogramming and Folly

String#constantize

(ActiveSupport only.)

Page 22: Metaprogramming and Folly

:constantize allows you to turn strings into constants

(including classes).

Page 23: Metaprogramming and Folly

(It’s essentially a special case of :eval.)

Page 24: Metaprogramming and Folly

Imagine you have some POROs in your app.

class Sword < Weapon def self.damage 10 endend

class Spear < Weapon def self.damage 6 endend

class Dagger < Weapon def self.damage 4 endend

class StronglyWordedEmail < Weapon def self.damage 1 endend

Page 25: Metaprogramming and Folly

This is a common pattern:

Player.first.weapon_type.constantize.damage # => 4

Player: { id: 1, hp: 50, weapon_type: 'Dagger',}

Page 26: Metaprogramming and Folly

Class#define_method

Page 27: Metaprogramming and Folly

:define_method allows you to create new instance

methods at runtime.

Page 28: Metaprogramming and Folly

class Player CHAINABLE_MOVES = [:slash, :swipe, :poke]

def slash 5 end

def swipe 3 end

def poke 1 endend

Page 29: Metaprogramming and Folly

class Player CHAINABLE_MOVES.permutation(2).each do |m1, m2| define_method("#{m1}_and_#{m2}") do send(m1) + send(m2) end endend

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

Page 30: Metaprogramming and Folly

Now this is definitely magical.

Page 31: Metaprogramming and Folly

But we can go deeper.

Page 32: Metaprogramming and Folly

Object#method_missing

Page 33: Metaprogramming and Folly

:method_missing is like a before_filter to NoMethodErrors.

Page 34: Metaprogramming and Folly

If you call a method that doesn’t exist, :method_missing is first

invoked.

Page 35: Metaprogramming and Folly

class Player def attack puts "Hiya!" end

def defend puts "Ouch." end

def retreat puts "AHHHHHHHHH" endend

Player.new.triple_attack => undefined method `triple_attack' for #<Player:0x007f971b8291a0> (NoMethodError)

Page 36: Metaprogramming and Folly

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 endend

Player.new.triple_attack => Hiya!=> Hiya!=> Hiya!

Let’s just add this.

Page 37: Metaprogramming and Folly

Pretty cool, right?

Page 38: Metaprogramming and Folly

Actually, this is terrible.

Never do this.

Page 39: Metaprogramming and Folly

Most metaprogramming has no place in a production codebase:

method_missingeval

instance_evalclass_eval

instance_variable_setconst_set

Page 40: Metaprogramming and Folly

Let’s talk why.

Page 41: Metaprogramming and Folly

Pros:

• Powerful DSLs

- You think you want this, but you probably don’t.

• Keeps things DRY!

- Be careful. You can go overboard.

Page 42: Metaprogramming and Folly

Cons:• Greppability?

• Greppability.

• Greppability…

• Greppability!!

Page 43: Metaprogramming and Folly

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 endend

> grep triple_attack *Player.rb:21:Player.new.triple_attack> ...wtf

Page 44: Metaprogramming and Folly

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.

Page 45: Metaprogramming and Folly

Metaprogramming is magical.

Page 46: Metaprogramming and Folly

But true magic demands that we all believe in it.

Page 47: Metaprogramming and Folly

Thanks for listening.

You can follow me at @hosseeb