Slide 1

Slide 1 text

Metaprogramming? Not good enough! Justin Weiss @justinweiss | #tinyobj

Slide 2

Slide 2 text

Metaprogramming? Not good enough! Justin Weiss @justinweiss | #tinyobj

Slide 3

Slide 3 text

@justinweiss | #tinyobj

Slide 4

Slide 4 text

@justinweiss | #tinyobj

Slide 5

Slide 5 text

@justinweiss | #tinyobj

Slide 6

Slide 6 text

@justinweiss | #tinyobj

Slide 7

Slide 7 text

→ 2005: Ruby @justinweiss | #tinyobj

Slide 8

Slide 8 text

→ 2005: Ruby → 2006: JavaScript Ruby @justinweiss | #tinyobj

Slide 9

Slide 9 text

→ 2005: Ruby → 2006: JavaScript Ruby → 2007: Erlang Ruby @justinweiss | #tinyobj

Slide 10

Slide 10 text

→ 2005: Ruby → 2006: JavaScript Ruby → 2007: Erlang Ruby → ... → 2015: Swift Ruby @justinweiss | #tinyobj

Slide 11

Slide 11 text

What makes Ruby, Ruby? @justinweiss | #tinyobj

Slide 12

Slide 12 text

@justinweiss | #tinyobj

Slide 13

Slide 13 text

Erlang = Actors @justinweiss | #tinyobj

Slide 14

Slide 14 text

Erlang = Actors Haskell = Monads @justinweiss | #tinyobj

Slide 15

Slide 15 text

Erlang = Actors Haskell = Monads Lisp = Parentheses @justinweiss | #tinyobj

Slide 16

Slide 16 text

Erlang = Actors Haskell = Monads Lisp = Parentheses Macros @justinweiss | #tinyobj

Slide 17

Slide 17 text

Erlang = Actors Haskell = Monads Lisp = Parentheses Macros Ruby = Metaprogramming? @justinweiss | #tinyobj

Slide 18

Slide 18 text

@justinweiss | #tinyobj

Slide 19

Slide 19 text

Metaprogramming → !? @justinweiss | #tinyobj

Slide 20

Slide 20 text

@justinweiss | #tinyobj

Slide 21

Slide 21 text

@justinweiss | #tinyobj

Slide 22

Slide 22 text

@justinweiss | #tinyobj

Slide 23

Slide 23 text

@justinweiss | #tinyobj

Slide 24

Slide 24 text

@justinweiss | #tinyobj

Slide 25

Slide 25 text

A brand new object model (on top of Ruby) @justinweiss | #tinyobj

Slide 26

Slide 26 text

Object model The concepts, data structures, and methods you use to build things in your language @justinweiss | #tinyobj

Slide 27

Slide 27 text

@justinweiss | #tinyobj

Slide 28

Slide 28 text

I. Piumarta and A. Warth Open, Extensible Object Models http://piumarta.com/software/cola/ objmodel2.pdf @justinweiss | #tinyobj

Slide 29

Slide 29 text

When you have a method name and arguments, which code do you use? @justinweiss | #tinyobj

Slide 30

Slide 30 text

When you have a method name and arguments, which code do you use? @justinweiss | #tinyobj

Slide 31

Slide 31 text

def lookup(object, method_name) # Look up a method by name in object's methods list # If it exists, return it # Otherwise, if you have a parent class, recurse end @justinweiss | #tinyobj

Slide 32

Slide 32 text

@justinweiss | #tinyobj

Slide 33

Slide 33 text

@justinweiss | #tinyobj

Slide 34

Slide 34 text

What is behavior? @justinweiss | #tinyobj

Slide 35

Slide 35 text

@justinweiss | #tinyobj

Slide 36

Slide 36 text

@justinweiss | #tinyobj

Slide 37

Slide 37 text

@justinweiss | #tinyobj

Slide 38

Slide 38 text

@justinweiss | #tinyobj

Slide 39

Slide 39 text

@justinweiss | #tinyobj

Slide 40

Slide 40 text

@justinweiss | #tinyobj

Slide 41

Slide 41 text

@justinweiss | #tinyobj

Slide 42

Slide 42 text

Object = a thing Class = a thing that builds objects, holds methods, has a parent Behavior = a thing that holds methods like lookup and add_method @justinweiss | #tinyobj

Slide 43

Slide 43 text

Object add_method lookup @justinweiss | #tinyobj

Slide 44

Slide 44 text

How do we get an object from a class? @justinweiss | #tinyobj

Slide 45

Slide 45 text

Object add_method lookup build_object @justinweiss | #tinyobj

Slide 46

Slide 46 text

What about new classes, sub-classes, or sub- behaviors? @justinweiss | #tinyobj

Slide 47

Slide 47 text

Object add_method lookup build_object delegate @justinweiss | #tinyobj

Slide 48

Slide 48 text

How do you call a method? @justinweiss | #tinyobj

Slide 49

Slide 49 text

Object add_method lookup build_object delegate send @justinweiss | #tinyobj

Slide 50

Slide 50 text

Let's build it in Ruby! @justinweiss | #tinyobj

Slide 51

Slide 51 text

class TinyObject < BasicObject attr_accessor :state attr_accessor :behavior end @justinweiss | #tinyobj

Slide 52

Slide 52 text

class TinyObject < BasicObject attr_accessor :state attr_accessor :behavior end @justinweiss | #tinyobj

Slide 53

Slide 53 text

Create the core methods @justinweiss | #tinyobj

Slide 54

Slide 54 text

behavior_add_method = lambda do |behavior, method_name, method| behavior.state[:methods][method_name] = method end @justinweiss | #tinyobj

Slide 55

Slide 55 text

behavior_add_method = lambda do |behavior, method_name, method| # -------^ behavior.state[:methods][method_name] = method end @justinweiss | #tinyobj

Slide 56

Slide 56 text

behavior_lookup = lambda do |behavior, method_name| # ... end @justinweiss | #tinyobj

Slide 57

Slide 57 text

behavior_lookup = lambda do |behavior, method_name| # Look up a method by name in the object's methods list method = behavior.state[:methods][method_name] # ... method end @justinweiss | #tinyobj

Slide 58

Slide 58 text

behavior_lookup = lambda do |behavior, method_name| # ... # If you can't find it, but have a parent class... if !method && behavior.state[:parent] # call this method recursively, using the parent instead method = behavior_lookup.call(behavior.state[:parent], method_name) end method end @justinweiss | #tinyobj

Slide 59

Slide 59 text

behavior_lookup = lambda do |behavior, method_name| # Look up a method by name in the object's methods list method = behavior.state[:methods][method_name] # If you can't find it, but have a parent class... if !method && behavior.state[:parent] # call this method recursively, using the parent instead method = behavior_lookup.call(behavior.state[:parent], method_name) end method end @justinweiss | #tinyobj

Slide 60

Slide 60 text

behavior_build_object = lambda do |behavior| # Allocate an object obj = TinyObject.new obj.state = {} # set the object's class / behavior # to yourself (the class that called this method) obj.behavior = behavior # return the object obj end @justinweiss | #tinyobj

Slide 61

Slide 61 text

behavior_delegate = lambda do |parent_class| # ... end @justinweiss | #tinyobj

Slide 62

Slide 62 text

@justinweiss | #tinyobj

Slide 63

Slide 63 text

subclass = behavior_build_object.call(parent_class.behavior) @justinweiss | #tinyobj

Slide 64

Slide 64 text

# set the object's parent (in its state) to ourselves subclass.state[:parent] = parent_class # initialize the object's state with an empty set of methods subclass.state[:methods] ||= {} subclass @justinweiss | #tinyobj

Slide 65

Slide 65 text

behavior_delegate = lambda do |parent_class| parent_class_behavior = parent_class && parent_class.behavior subclass = behavior_build_object.call(parent_class_behavior) # set the object's parent (in its state) to ourselves subclass.state[:parent] = parent_class # initialize the object's state with an empty set of methods subclass.state[:methods] ||= {} subclass end @justinweiss | #tinyobj

Slide 66

Slide 66 text

TinyObject: Our object structure, containing behavior and state behavior_add_method: Add a method to a class behavior_lookup: Find an implementation, from a method name behavior_build_object: Like .new, build an object from a class / behavior behavior_delegate: Inherit from a class / behavior @justinweiss | #tinyobj

Slide 67

Slide 67 text

@justinweiss | #tinyobj

Slide 68

Slide 68 text

default_behavior = behavior_delegate.call(nil) # ... @justinweiss | #tinyobj

Slide 69

Slide 69 text

default_behavior = behavior_delegate.call(nil) default_behavior.behavior = default_behavior @justinweiss | #tinyobj

Slide 70

Slide 70 text

root_object_class = behavior_delegate.call(nil) root_object_class.behavior = default_behavior @justinweiss | #tinyobj

Slide 71

Slide 71 text

root_object_class = behavior_delegate.call(nil) root_object_class.behavior = default_behavior @justinweiss | #tinyobj

Slide 72

Slide 72 text

# ... default_behavior.state[:parent] = root_object_class @justinweiss | #tinyobj

Slide 73

Slide 73 text

default_behavior = behavior_delegate.call(nil) default_behavior.behavior = default_behavior root_object_class = behavior_delegate.call(nil) root_object_class.behavior = default_behavior default_behavior.state[:parent] = root_object_class @justinweiss | #tinyobj

Slide 74

Slide 74 text

behavior_add_method.call(default_behavior, "lookup", behavior_lookup) behavior_add_method.call(default_behavior, "add_method", behavior_add_method) behavior_add_method.call(default_behavior, "build_object", behavior_build_object) behavior_add_method.call(default_behavior, "delegate", behavior_delegate) @justinweiss | #tinyobj

Slide 75

Slide 75 text

@justinweiss | #tinyobj

Slide 76

Slide 76 text

@justinweiss | #tinyobj

Slide 77

Slide 77 text

object_send = lambda do |object, method_name, *args| method = find_method.call(object, method_name) if method method.call(object, *args) else raise "No method #{method_name} found on object" end end @justinweiss | #tinyobj

Slide 78

Slide 78 text

find_method = lambda do |object, method_name| object_send.call(object.behavior, "lookup", method_name) end @justinweiss | #tinyobj

Slide 79

Slide 79 text

find_method = lambda do |object, method_name| if (object == default_behavior && method_name == "lookup") behavior_lookup.call(default_behavior, "lookup") else object_send.call(object.behavior, "lookup", method_name) end end @justinweiss | #tinyobj

Slide 80

Slide 80 text

behavior_add_method: Add a method to a class behavior_lookup: Find an implementation, from a method name behavior_build_object: Like .new, build an object from a class / behavior behavior_delegate: Inherit from a class / behavior object_send: Call a method, by name, on an object @justinweiss | #tinyobj

Slide 81

Slide 81 text

greeter_class = object_send.call(root_object_class, "delegate") @justinweiss | #tinyobj

Slide 82

Slide 82 text

greeter_class = object_send.call(root_object_class, "delegate") object_send.call(greeter_class, "add_method", "hello", lambda { |object| puts "Hello, world!" }) @justinweiss | #tinyobj

Slide 83

Slide 83 text

greeter = object_send.call(greeter_class, "build_object") object_send.call(greeter, "hello") # => @justinweiss | #tinyobj

Slide 84

Slide 84 text

greeter = object_send.call(greeter_class, "build_object") object_send.call(greeter, "hello") # => "Hello, world!" @justinweiss | #tinyobj

Slide 85

Slide 85 text

@justinweiss | #tinyobj

Slide 86

Slide 86 text

greeter = object_send.call(greeter_class, "build_object") object_send.call(greeter, "hello") # => "Hello, world!" @justinweiss | #tinyobj

Slide 87

Slide 87 text

@justinweiss | #tinyobj

Slide 88

Slide 88 text

class TinyObject < BasicObject attr_accessor :state attr_accessor :behavior def method_missing(name, *args) object_send = nil behavior = self.behavior # Crawl our ancestors for an "object_send" implementation while !object_send && behavior object_send = behavior.state[:methods]["object_send"] behavior = behavior.state[:parent] end # Call object_send on ourselves object_send.call(self, name.to_s, *args) end end @justinweiss | #tinyobj

Slide 89

Slide 89 text

object_send.call(greeter, "hello") @justinweiss | #tinyobj

Slide 90

Slide 90 text

object_send.call(greeter, "hello") ⬇ greeter.hello @justinweiss | #tinyobj

Slide 91

Slide 91 text

behavior_add_method.call(root_object_class, "object_send", object_send) @justinweiss | #tinyobj

Slide 92

Slide 92 text

greeter_class.add_method("hello_name", lambda { |object| puts "Hello, #{object.state[:name]}!" }) # ----^ @justinweiss | #tinyobj

Slide 93

Slide 93 text

alice = greeter_class.build_object bob = greeter_class.build_object alice.state[:name] = "Alice" bob.state[:name] = "Bob" alice.hello_name # => bob.hello_name # => @justinweiss | #tinyobj

Slide 94

Slide 94 text

alice = greeter_class.build_object bob = greeter_class.build_object alice.state[:name] = "Alice" bob.state[:name] = "Bob" alice.hello_name # => "Hello, Alice!" bob.hello_name # => "Hello, Bob!" @justinweiss | #tinyobj

Slide 95

Slide 95 text

Now what? @justinweiss | #tinyobj

Slide 96

Slide 96 text

Log method calls! @justinweiss | #tinyobj

Slide 97

Slide 97 text

def lookup # Get the old lookup method from the parent of our behavior # Call it to find the real method implementation # Wrap the method and return it end @justinweiss | #tinyobj

Slide 98

Slide 98 text

method_logger = lambda do |method_name, method, *args| puts "> Calling #{method_name}... " method.call(*args) puts "> Done!" end @justinweiss | #tinyobj

Slide 99

Slide 99 text

@justinweiss | #tinyobj

Slide 100

Slide 100 text

intercepting_behavior = default_behavior.delegate @justinweiss | #tinyobj

Slide 101

Slide 101 text

intercepting_behavior.add_method( "lookup", lambda do |sender, method_name| # Get the old lookup method from the parent of our class's behavior lookup = object_send.call(sender.behavior.state[:parent], "lookup", "lookup") # Call it to find the real method implementation method = lookup.call(sender, method_name) # Wrap the method and return it if method original_method = method interceptor = sender.state[:interceptor_method] method = lambda { |*args| interceptor.call(method_name, original_method, *args) } end method end ) @justinweiss | #tinyobj

Slide 102

Slide 102 text

# Get the old lookup method # from the parent of our class's behavior super_behavior = ? lookup = super_behavior.lookup("lookup") @justinweiss | #tinyobj

Slide 103

Slide 103 text

super_behavior = sender.behavior.state[:parent] lookup = super_behavior.lookup("lookup") @justinweiss | #tinyobj

Slide 104

Slide 104 text

# Get the old lookup method # from the parent of our class's behavior super_behavior = sender.behavior.state[:parent] lookup = super_behavior.lookup("lookup") # Call lookup to find the real method implementation method = lookup.call(sender, method_name) @justinweiss | #tinyobj

Slide 105

Slide 105 text

# Wrap the method and return it if method original_method = method interceptor = sender.state[:interceptor_method] method = lambda do |*args| interceptor.call(method_name, original_method, *args) end end method @justinweiss | #tinyobj

Slide 106

Slide 106 text

@justinweiss | #tinyobj

Slide 107

Slide 107 text

person_class = root_object_class.delegate # ... @justinweiss | #tinyobj

Slide 108

Slide 108 text

person_class = root_object_class.delegate person_class.behavior = intercepting_behavior @justinweiss | #tinyobj

Slide 109

Slide 109 text

person_class = root_object_class.delegate # ... @justinweiss | #tinyobj

Slide 110

Slide 110 text

person_class = root_object_class.delegate person_class.behavior = intercepting_behavior @justinweiss | #tinyobj

Slide 111

Slide 111 text

method_logger = lambda do |method_name, method, *args| puts "> Calling #{method_name}... " method.call(*args) puts "> Done!" end person_class.state[:interceptor_method] = method_logger @justinweiss | #tinyobj

Slide 112

Slide 112 text

person_class.add_method "name", lambda { |_| puts "Justin" } person_class.add_method "location", lambda { |_| puts "Cincinnati, OH" } @justinweiss | #tinyobj

Slide 113

Slide 113 text

person = person_class.build_object person.name # # # person.location # # # @justinweiss | #tinyobj

Slide 114

Slide 114 text

person = person_class.build_object person.name # > Calling name... # Justin # > Done! person.location # > Calling location... # Cincinnati, OH # > Done! @justinweiss | #tinyobj

Slide 115

Slide 115 text

@justinweiss | #tinyobj

Slide 116

Slide 116 text

person_class.behavior = default_behavior person.name # @justinweiss | #tinyobj

Slide 117

Slide 117 text

person_class.behavior = default_behavior person.name # Justin @justinweiss | #tinyobj

Slide 118

Slide 118 text

Retry method calls! @justinweiss | #tinyobj

Slide 119

Slide 119 text

person_class.add_method("flaky_method", lambda do |_| if rand(3) == 0 puts "Success!" true else false end end) @justinweiss | #tinyobj

Slide 120

Slide 120 text

retry_interceptor = lambda do |method_name, method, *args| until method.call(*args) puts "Method #{method_name} failed, retrying..." sleep 0.5 end end @justinweiss | #tinyobj

Slide 121

Slide 121 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method @justinweiss | #tinyobj

Slide 122

Slide 122 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method # Method flaky_method failed, retrying... @justinweiss | #tinyobj

Slide 123

Slide 123 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... @justinweiss | #tinyobj

Slide 124

Slide 124 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... @justinweiss | #tinyobj

Slide 125

Slide 125 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... @justinweiss | #tinyobj

Slide 126

Slide 126 text

person_class.state[:interceptor_method] = retry_interceptor person.flaky_method # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Method flaky_method failed, retrying... # Success! @justinweiss | #tinyobj

Slide 127

Slide 127 text

@justinweiss | #tinyobj

Slide 128

Slide 128 text

Multiple Inheritance @justinweiss | #tinyobj

Slide 129

Slide 129 text

@justinweiss | #tinyobj

Slide 130

Slide 130 text

@justinweiss | #tinyobj

Slide 131

Slide 131 text

@justinweiss | #tinyobj

Slide 132

Slide 132 text

@justinweiss | #tinyobj

Slide 133

Slide 133 text

multiple_parents_behavior = default_behavior.delegate @justinweiss | #tinyobj

Slide 134

Slide 134 text

multiple_parents_behavior.add_method("add_parent", lambda do |behavior, new_parent| # Initialize `parents` with the original parent unless behavior.state[:parents] behavior.state[:parents] = [] parent = behavior.state[:parent] behavior.state[:parents] << parent if parent end # Append the new parent to `parents` behavior.state[:parents] << new_parent end ) @justinweiss | #tinyobj

Slide 135

Slide 135 text

multiple_parents_behavior.add_method( "lookup", lambda do |behavior, method_name| method = behavior.state[:methods][method_name] parents = behavior.state[:parents] || [behavior.state[:parent]] if !method && parents parents.each do |parent| break if method = parent.lookup(method_name) end end method end ) @justinweiss | #tinyobj

Slide 136

Slide 136 text

multiple_parents_behavior.add_method( "lookup", lambda do |behavior, method_name| method = behavior.state[:methods][method_name] parents = behavior.state[:parents] || [behavior.state[:parent]] if !method && parents parents.each do |parent| # v---- -----^ break if method = parent.lookup(method_name) end end method end ) @justinweiss | #tinyobj

Slide 137

Slide 137 text

person_class = root_object_class.delegate person_class.add_method "name", lambda { |_| puts "Justin" } greeter_class = root_object_class.delegate greeter_class.add_method "greeting", lambda { |_| puts "Hello! Hello! Hello!" } @justinweiss | #tinyobj

Slide 138

Slide 138 text

social_person_class = person_class.delegate social_person_class.behavior = multiple_parents_behavior social_person_class.add_parent(greeter_class) @justinweiss | #tinyobj

Slide 139

Slide 139 text

justin = social_person_class.build_object justin.name # => justin.greeting # => @justinweiss | #tinyobj

Slide 140

Slide 140 text

justin = social_person_class.build_object justin.name # => "Justin" justin.greeting # => "Hello! Hello! Hello!" @justinweiss | #tinyobj

Slide 141

Slide 141 text

→ Single inheritance → Interception → Multiple Inheritance @justinweiss | #tinyobj

Slide 142

Slide 142 text

→ Object → add_method → lookup → build_object → delegate → send @justinweiss | #tinyobj

Slide 143

Slide 143 text

Modules? method_missing? BasicObject? delegation? @justinweiss | #tinyobj

Slide 144

Slide 144 text

Intent @justinweiss | #tinyobj

Slide 145

Slide 145 text

What's normal? @justinweiss | #tinyobj

Slide 146

Slide 146 text

@justinweiss | #tinyobj

Slide 147

Slide 147 text

@justinweiss | #tinyobj

Slide 148

Slide 148 text

What? Why!? @justinweiss | #tinyobj

Slide 149

Slide 149 text

@justinweiss | #tinyobj

Slide 150

Slide 150 text

The best way to understand a system is to break it. @justinweiss | #tinyobj

Slide 151

Slide 151 text

symbols.each do |symbol| klass = Class.new(ActiveRecord::Base) do def method_missing(name, *args) # ... end end Object.const_set(symbol.to_s.capitalize, klass) end @justinweiss | #tinyobj

Slide 152

Slide 152 text

@justinweiss | #tinyobj

Slide 153

Slide 153 text

@justinweiss | #tinyobj

Slide 154

Slide 154 text

Try it out @justinweiss | #tinyobj

Slide 155

Slide 155 text

Thank you! Justin Weiss @justinweiss https://www.avvo.com justin@justinweiss.com https://www.justinweiss.com/ rubyconf-2016 @justinweiss | #tinyobj

Slide 156

Slide 156 text

Thank you! Justin Weiss @justinweiss https://www.avvo.com justin@justinweiss.com https://www.justinweiss.com/ rubyconf-2016 @justinweiss | #tinyobj

Slide 157

Slide 157 text

The Ruby logo is Copyright © 2006, Yukihiro Matsumoto. It is licensed under the terms of the Creative Commons Attribution- ShareAlike 2.5 License agreement. "Happy Programmers" by Jesper Rønn-Jensen, used under CC BY- SA 2.0 / Resized from original "Laptop Stickers" by Nate Angell, used under CC BY 2.0 / Resized from original "No Brain" by Pierre-Olivier Carles, used under CC BY 2.0 @justinweiss | #tinyobj