Metaprogramming for Generalists

Metaprogramming for Generalists

05fdba1ae381f24512e977f8fe2697b4?s=128

Chris Salzberg

August 24, 2018
Tweet

Transcript

  1. 2.

    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.
  2. 8.

    metaprogramming (noun) 1. writing code that writes code 2. technique

    in which computer programs have the ability to treat programs as their data
  3. 9.

    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. 10.

    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
  5. 11.
  6. 14.
  7. 15.
  8. 19.
  9. 24.
  10. 26.

    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
  11. 27.

    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
  12. 28.

    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
  13. 29.

    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
  14. 30.

    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
  15. 31.

    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
  16. 32.

    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
  17. 34.

    application library control knockout - abstraction invisible - send as

    message - speaks in language of domain - abstraction visible - send as argument - speaks in language of abstraction
  18. 35.

    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
  19. 49.

    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"
  20. 50.

    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"
  21. 51.

    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
  22. 55.

    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
  23. 56.
  24. 57.
  25. 59.

    λ ~/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
  26. 60.

    λ ~/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
  27. 61.

    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
  28. 62.

    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
  29. 63.

    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
  30. 64.

    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
  31. 65.

    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
  32. 66.

    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)
  33. 68.

    class Address def ==(other) keys = [:street, :city, :country] keys.all?

    { |key| other.send(key) == send(key) } end end
  34. 69.

    class Address define_method :== do |other| keys = [:street, :city,

    :country] keys.all? { |key| other.send(key) == send(key) } end end
  35. 70.

    class Address keys = [:street, :city, :country] define_method :== do

    |other| keys.all? { |key| other.send(key) == send(key) } end end
  36. 71.

    class Address keys = [:street, :city, :country] define_method :== do

    |other| keys.all? { |key| other.send(key) == send(key) } end end method arguments
  37. 72.

    class Address keys = [:street, :city, :country] define_method :== do

    |other| keys.all? { |key| other.send(key) == send(key) } end end method body
  38. 73.

    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
  39. 74.

    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
  40. 77.

    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
  41. 78.
  42. 79.

    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 #=> #<Address street="Neubaugürtel 34-36" city="Wien" address="Austria" > euruko2018 = Address.new("Neubaugürtel 34-36", "Wein", "Austria") euruko2018.inspect #=> #<Address street="Neubaugürtel 34-36" city="Wien" address="Austria" >
  43. 80.

    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 #=> { #<GpsLocation lat=1 long=2>=>2 } point_a = GpsLocation.new(1, 2) point_b = GpsLocation.new(1, 2) visits = {} visits[point_a] = 1 visits[point_b] = 2 visits #=> { #<GpsLocation lat=1 long=2>=>2 } build on abstraction
  44. 81.
  45. 82.
  46. 88.

    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