Slide 1

Slide 1 text

FOR GENERALISTS metaprogramming CHRIS SALZBERG

Slide 2

Slide 2 text

about me ● My handle is @shioyama. ● I live in Tokyo, Japan. ● I’m a Canadian from Montréal. ● I work at a company called Degica. ● I’m the author of a gem called Mobility. ● I blog at dejimata.com.

Slide 3

Slide 3 text

1. generalist metaprogramming 2. building generic software

Slide 4

Slide 4 text

1 generalist metaprogramming

Slide 5

Slide 5 text

metaprogramming

Slide 6

Slide 6 text

metaprogramming (noun)

Slide 7

Slide 7 text

metaprogramming (noun) 1. writing code that writes code

Slide 8

Slide 8 text

metaprogramming (noun) 1. writing code that writes code 2. technique in which computer programs have the ability to treat programs as their data

Slide 9

Slide 9 text

metaprogramming (noun) 1. writing code that writes code 2. technique in which computer programs have the ability to treat programs as their data 3. a way for a program to find out things about itself, or other programs

Slide 10

Slide 10 text

metaprogramming (noun) 1. writing code that writes code 2. technique in which computer programs have the ability to treat programs as their data 3. a way for a program to find out things about itself, or other programs 4. a bag of tricks

Slide 11

Slide 11 text

then

Slide 12

Slide 12 text

define_method Class.new class_eval instance_eval eval method_missing Module.new binding arity parameters method_added method send

Slide 13

Slide 13 text

(Normal) PROGRAMMING metaprogramming

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

ruby ruby

Slide 16

Slide 16 text

ruby ruby applications applications

Slide 17

Slide 17 text

ruby ruby applications applications libraries libraries

Slide 18

Slide 18 text

ruby ruby applications applications metaprogramming lives here libraries libraries

Slide 19

Slide 19 text

why?

Slide 20

Slide 20 text

generalization

Slide 21

Slide 21 text

generalization (noun) Formulation of general concepts from specific instances by abstracting common properties.

Slide 22

Slide 22 text

generalization (noun) Formulation of general concepts from specific instances by abstracting common properties.

Slide 23

Slide 23 text

generalization (noun) Formulation of general concepts from specific instances by abstracting common properties.

Slide 24

Slide 24 text

attribute

Slide 25

Slide 25 text

knockout experiment

Slide 26

Slide 26 text

class Model def initialize; @attributes = {}; end def self.define_attribute(name) define_method name do @attributes.fetch(name) end define_method "#{name}=" do |value| @attributes[name] = value end end end class Model def initialize; @attributes = {}; end def self.define_attribute(name) define_method name do @attributes.fetch(name) end define_method "#{name}=" do |value| @attributes[name] = value end end end control

Slide 27

Slide 27 text

class Talk < Model define_attribute :title define_attribute :abstract end talk = Talk.new talk.title = "Metaprogramming for Generalists" talk.title #=> "Metaprogramming for Generalists" talk.abstract = "It conjures up images of..." talk.abstract #=> "It conjures up images of..." control

Slide 28

Slide 28 text

class Talk < Model define_attribute :title define_attribute :abstract end talk = Talk.new talk.title = "Metaprogramming for Generalists" talk.title #=> "Metaprogramming for Generalists" talk.abstract = "It conjures up images of..." talk.abstract #=> "It conjures up images of..." abstraction is transparent control

Slide 29

Slide 29 text

class Model def initialize; @attributes = {}; end def get_attribute(name) @attributes.fetch(name) end def set_attribute(name, value) @attributes[name] = value end end class Model def initialize; @attributes = {}; end def get_attribute(name) @attributes.fetch(name) end def set_attribute(name, value) @attributes[name] = value end end knockout

Slide 30

Slide 30 text

class Talk < Model end talk = Talk.new talk.set_attribute(:title, "MP for Generalists") talk.get_attribute(:title) #=> "MP for Generalists" talk.set_attribute(:abstract, "It conjures...") talk.get_attribute(:abstract) #=> "It conjures..." knockout

Slide 31

Slide 31 text

class Talk < Model end talk = Talk.new talk.set_attribute(:title, "MP for Generalists") talk.get_attribute(:title) #=> "MP for Generalists" talk.set_attribute(:abstract, "It conjures...") talk.get_attribute(:abstract) #=> "It conjures..." abstraction is visible knockout

Slide 32

Slide 32 text

class Talk < Model end talk = Talk.new talk.set_attribute(:title, "MP for Generalists") talk.get_attribute(:title) #=> "MP for Generalists" talk.set_attribute(:abstract, "It conjures...") talk.get_attribute(:abstract) #=> "It conjures..." knockout names are arguments

Slide 33

Slide 33 text

application library control knockout

Slide 34

Slide 34 text

application library control knockout - abstraction invisible - send as message - speaks in language of domain - abstraction visible - send as argument - speaks in language of abstraction

Slide 35

Slide 35 text

application library control knockout - abstraction invisible - send as message - speaks in language of domain - abstraction visible - send as argument - speaks in language of abstraction - unknowns refer to code - hard to understand - unknowns do not refer to code - easier to understand

Slide 36

Slide 36 text

talk = Talk.find(...) first_name = talk.get_association(:speaker) .get_attribute(:first_name) comment = talk.get_association(:comments).build comment.set_attribute( :content, "#{first_name} is speaking")

Slide 37

Slide 37 text

would you use this?

Slide 38

Slide 38 text

talk = Talk.find(...) comment = talk.comments.build comment.content = "#{talk.speaker.first_name} is speaking")

Slide 39

Slide 39 text

metaprogramming levels the playing field by enabling libraries to translate unknowns into code

Slide 40

Slide 40 text

eval("foo")

Slide 41

Slide 41 text

eval("foo") foo

Slide 42

Slide 42 text

str = "..." eval("foo#{str}")

Slide 43

Slide 43 text

def fooval(str) eval("foo#{str}") end

Slide 44

Slide 44 text

attr_writer "foo"

Slide 45

Slide 45 text

attr_writer "foo" def foo=(value) @foo = value end

Slide 46

Slide 46 text

def define_writer(name) define_method("#{name}=") do |value| instance_variable_set(:"@#{name}", value) end end

Slide 47

Slide 47 text

def define_writer(name) attr_writer name end

Slide 48

Slide 48 text

control control contains metaprogramming method(s) knockout knockout

Slide 49

Slide 49 text

control control contains metaprogramming method(s) knockout knockout define_writer define_writer[2] [2] define_writer define_writer[1] [1] fooval fooval attr_writer "foo" attr_writer "foo" eval "foo" eval "foo"

Slide 50

Slide 50 text

control control contains metaprogramming method(s) knockout knockout define_writer define_writer[2] [2] define_writer define_writer[1] [1] fooval fooval attr_writer "foo" attr_writer "foo" not reducible to “normal” programming eval "foo" eval "foo"

Slide 51

Slide 51 text

begin module rescue def nil false class do case self @foo true end for while if else ensure until “normal programming” = cannot convert unknowns into code

Slide 52

Slide 52 text

(meta) PROGRAMMING “normal” programming

Slide 53

Slide 53 text

(meta) PROGRAMMING generic software

Slide 54

Slide 54 text

2 building generic software

Slide 55

Slide 55 text

Jeremy Evans One of the best ways to write flexible software is to write generic software. Instead of designing a single API that completely handles a specific case, you write multiple APIs that handle smaller, more generic parts of that use case and then handling the entire case is just gluing those parts together.” “ “The Development of Sequel”, May 2012

Slide 56

Slide 56 text

equality

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

λ ~/dev/rails/ master ag "def ==[^\=]" -l

Slide 59

Slide 59 text

λ ~/dev/rails/ master ag "def ==[^\=]" -l activejob/test/cases/serializers_test.rb activejob/test/models/person.rb activemodel/lib/active_model/attribute_set.rb activemodel/lib/active_model/attribute_set/builder.rb activemodel/lib/active_model/attribute.rb activemodel/lib/active_model/type/value.rb activemodel/lib/active_model/type/binary.rb actionpack/lib/action_dispatch/middleware/stack.rb actionpack/lib/action_dispatch/http/mime_type.rb actionpack/lib/action_controller/metal/strong_parameters.rb actionview/lib/action_view/template/types.rb activesupport/lib/active_support/duration.rb activerecord/test/models/customer.rb activerecord/lib/active_record/associations/collection_proxy.rb activerecord/lib/active_record/relation.rb activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb activerecord/lib/active_record/connection_adapters/postgresql/utils.rb activerecord/lib/active_record/connection_adapters/column.rb activerecord/lib/active_record/core.rb activerecord/lib/active_record/relation/where_clause.rb activerecord/lib/active_record/reflection.rb activerecord/lib/active_record/association_relation.rb activerecord/lib/active_record/aggregations.rb

Slide 60

Slide 60 text

λ ~/dev/rails/ master ag "def ==[^\=]" -l activejob/test/cases/serializers_test.rb activejob/test/models/person.rb activemodel/lib/active_model/attribute_set.rb activemodel/lib/active_model/attribute_set/builder.rb activemodel/lib/active_model/attribute.rb activemodel/lib/active_model/type/value.rb activemodel/lib/active_model/type/binary.rb actionpack/lib/action_dispatch/middleware/stack.rb actionpack/lib/action_dispatch/http/mime_type.rb actionpack/lib/action_controller/metal/strong_parameters.rb actionview/lib/action_view/template/types.rb activesupport/lib/active_support/duration.rb activerecord/test/models/customer.rb activerecord/lib/active_record/associations/collection_proxy.rb activerecord/lib/active_record/relation.rb activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb activerecord/lib/active_record/connection_adapters/postgresql/utils.rb activerecord/lib/active_record/connection_adapters/column.rb activerecord/lib/active_record/core.rb activerecord/lib/active_record/relation/where_clause.rb activerecord/lib/active_record/reflection.rb activerecord/lib/active_record/association_relation.rb activerecord/lib/active_record/aggregations.rb

Slide 61

Slide 61 text

class Address attr_reader :street, :city, :country def initialize(street, city, country) @street, @city, @country = street, city, country end # ... def ==(other) other.is_a?(self.class) && other.street == street && other.city == city && other.country == country end end # activerecord/test/models/customer.rb:15

Slide 62

Slide 62 text

class GpsLocation attr_reader :gps_location # ... def latitude gps_location.split("x").first end def longitude gps_location.split("x").last end def ==(other) other.latitude == latitude && other.longitude == longitude end end # activerecord/test/models/customer.rb:45

Slide 63

Slide 63 text

class Address def ==(other) other.is_a?(self.class) && other.street == street && other.city == city && other.country == country end end class GpsLocation def ==(other) other.latitude == latitude && other.longitude == longitude end end

Slide 64

Slide 64 text

class Address def ==(other) other.street == street && other.city == city && other.country == country end end class GpsLocation def ==(other) other.latitude == latitude && other.longitude == longitude end end

Slide 65

Slide 65 text

class Address def ==(other) other.send(:street) == send(:street) && other.send(:city) == send(:city) && other.send(:country) == send(:country) end end class GpsLocation def ==(other) other.send(:latitude) == send(:latitude) && other.send(:longitude) == send(:longitude) end end

Slide 66

Slide 66 text

class Address def ==(other) [:street, :city, :country].all? { |key| other.send(key) == send(key) } end end other.send(:street) == send(:street) && other.send(:city) == send(:city) && other.send(:country) == send(:country)

Slide 67

Slide 67 text

class Address def ==(other) [:street, :city, :country].all? { |key| other.send(key) == send(key) } end end abstraction

Slide 68

Slide 68 text

class Address def ==(other) keys = [:street, :city, :country] keys.all? { |key| other.send(key) == send(key) } end end

Slide 69

Slide 69 text

class Address define_method :== do |other| keys = [:street, :city, :country] keys.all? { |key| other.send(key) == send(key) } end end

Slide 70

Slide 70 text

class Address keys = [:street, :city, :country] define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end

Slide 71

Slide 71 text

class Address keys = [:street, :city, :country] define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end method arguments

Slide 72

Slide 72 text

class Address keys = [:street, :city, :country] define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end method body

Slide 73

Slide 73 text

class Address def self.equalize(*keys) define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end equalize :street, :city, :country end

Slide 74

Slide 74 text

module Equalizer def equalize(*keys) define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end end class Address extend Equalizer equalize :street, :city, :country end unknowns

Slide 75

Slide 75 text

require "equalizer" class Address extend Equalizer equalize :street, :city, :country end

Slide 76

Slide 76 text

generic softw Equalizer Address

Slide 77

Slide 77 text

class Address extend Equalizer equalize :street, :city, :country end class Address extend Equalizer equalize :street, :city, :country end class GpsLocation extend Equalizer equalize :latitude, :longitude end class GpsLocation extend Equalizer equalize :latitude, :longitude end require "equalizer" class AM::Type::Value extend Equalizer equalize :precision, :scale, :limit end class AM::Type::Value extend Equalizer equalize :precision, :scale, :limit end class AR::Attribute extend Equalizer equalize :name, :value_before_type_cast, :type end class AR::Attribute extend Equalizer equalize :name, :value_before_type_cast, :type end class AM::AttributeSet extend Equalizer equalize :attributes end class AM::AttributeSet extend Equalizer equalize :attributes end class AR::ConnectionAdapters::Column extend Equalizer equalize :attributes_for_hash end class AR::ConnectionAdapters::Column extend Equalizer equalize :attributes_for_hash end class AM::AttributeSet::Builder extend Equalizer equalize :materialize end class AM::AttributeSet::Builder extend Equalizer equalize :materialize end class AR::Relation::WhereClause extend Equalizer equalize :predicates end class AR::Relation::WhereClause extend Equalizer equalize :predicates end

Slide 78

Slide 78 text

module Equalizer def equalize(*keys) define_method :== do |other| keys.all? { |key| other.send(key) == send(key) } end end end

Slide 79

Slide 79 text

module Equalizer def equalize(*keys) define_method :== do |other|; ... ; end define_method :inspect do "#<#{self.class.name}#{keys.map { |key| " #{key}=#{send(key).inspect} " }.join}>" end end end build on abstraction euruko2018 = Address.new("Neubaugürtel 34-36", "Wein", "Austria") euruko2018.inspect #=> #
euruko2018 = Address.new("Neubaugürtel 34-36", "Wein", "Austria") euruko2018.inspect #=> #

Slide 80

Slide 80 text

module Equalizer def equalize(*keys) define_method :== do |other|; ... ; end define_method :inspect do; ... ; end define_method :hash do keys.map({ |key| send(key) }).hash end end end point_a = GpsLocation.new(1, 2) point_b = GpsLocation.new(1, 2) visits = {} visits[point_a] = 1 visits[point_b] = 2 visits #=> { #=>2 } point_a = GpsLocation.new(1, 2) point_b = GpsLocation.new(1, 2) visits = {} visits[point_a] = 1 visits[point_b] = 2 visits #=> { #=>2 } build on abstraction

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

To be a generalist

Slide 84

Slide 84 text

To be a generalist generalist

Slide 85

Slide 85 text

To be a generalist generalist

Slide 86

Slide 86 text

To be a generalist generalists

Slide 87

Slide 87 text

Chris Salzberg @shioyama / dejimata.com

Slide 88

Slide 88 text

credits ● Satellite image of Tokyo neighbourhood in self-intro used Google Maps ● Visualization of dry-rb gems network created using Graph Commons (graphcommons.com) ● Image of Ruby from: – http://pngimg.com/download/22155