Slide 1

Slide 1 text

Объектная модель Ruby а также метапрограммирование и создание DSL Дмитрий Цепелев, Backend в Evil Martians

Slide 2

Slide 2 text

MergeConf’22 2 @dmitrytsepelev 🌎 dmitrytsepelev.dev

Slide 3

Slide 3 text

MergeConf’22 Что почитать 3

Slide 4

Slide 4 text

Объектная модель Ruby

Slide 5

Slide 5 text

MergeConf’22 Виртуальная машина и байткод • Ruby – интерпретируемый язык; • компиляция — процесс перевода кода из одного языка в другой; • интерпретация – запуск кода из исходников; • интерпретатор Ruby (>=1.9) преобразует исходный код в байткод перед исполниенем; • байткод выполняется виртуальной машиной; • виртуальная машина знает как запустить байткод на конкретной машине; • Ruby – интерпретируемый язык? 🙂 5

Slide 6

Slide 6 text

MergeConf’22 Виртуальная машина и байткод 6 code = < < CODE puts 2+2 CODE puts RubyVM : : InstructionSequence.compile(code).disasm 0000 putself ( 1)[Li] 0001 putobject 2 0003 putobject 2 0005 opt_plus 0007 opt_send_without_block 0009 leave

Slide 7

Slide 7 text

MergeConf’22 Объект в Ruby • инкапсуляция — данные и методы работы с ними хранятся в одной сущности; • метод — набор инструкций байткода, хранится в памяти; • копирование байткода в каждый объект класса — дорого; • объект — указатель на класс и массив переменных объекта. 7

Slide 8

Slide 8 text

MergeConf’22 Класс в Ruby • объект, который содержит объявления методов, имена атрибутов, указатель на суперкласс и таблицу констант; • объект класса Class. 8 irb(main) : 003 : 0> Integer.class = > Class

Slide 9

Slide 9 text

MergeConf’22 Наследование • у каждого класса есть опциональный базовый класс; • методы базового класса доступны в наследнике; • базовый класс по умолчанию – Object. 9

Slide 10

Slide 10 text

MergeConf’22 Наследование 10 class ParentClass def parent_method; 'parent_method'; end end class ChildClass < ParentClass def child_method; 'child_method'; end end ParentClass.new.methods # = > [:parent_method, . . . ] ChildClass.superclass # = > ParentClass ChildClass.new.methods # = > [:child_method, :parent_method, . . . ] # child has access to parent methods ChildClass.new.parent_method # = > 'parent_method'

Slide 11

Slide 11 text

MergeConf’22 Где хранятся методы? • инстанс методы хранятся в объекте SomeClass; • методы класса должны быть где–то в базовом классе; • мы не можем добавить их прямо в Class; • Ruby генерирует для нас singleton_class/metaclass/eigenclass. 11 class SomeClass def self.class_method; 'class_method'; end def instance_method; 'instance_method'; end end instance = SomeClass.new instance.methods # = > [:instance_method, . . . ] SomeClass.singleton_class # = > # instance.singleton_class.methods # = > [:class_method, . . . ]

Slide 12

Slide 12 text

MergeConf’22 Цепочка поиска метода • singleton class; • модули; • методы в самом классе; • родительские методы (до корня). 12

Slide 13

Slide 13 text

MergeConf’22 Модули: include • модуль — объект, который содержит объявления методов, указатель на базовый класс и таблицу констант; • при включении модуля создается новый класс и добавляется в цепочку наследования как базовый класс для текущего. 13

Slide 14

Slide 14 text

MergeConf’22 14 module Raptor def name "🦖" end end class User attr_reader :name include Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => ???

Slide 15

Slide 15 text

MergeConf’22 Модули: класс переопределяет метод из включенного модуля 15 module Raptor def name "🦖" end end class User attr_reader :name include Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => Ivan

Slide 16

Slide 16 text

MergeConf’22 Модули: prepend 16 module Raptor def name “#{super} (🦖)” end end class User attr_reader :name prepend Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => “Ivan (🦖)" Prepend добавляет класс в цепочку наследования перед текущим

Slide 17

Slide 17 text

MergeConf’22 Модули: extend extend = include в singleton_class 17 class OtherClass extend M end # то же самое: class OtherClass class < < self include M end end OtherClass.new.methods # = > [ : …] OtherClass.new.singleton_class.methods # = > [:method_from_module, . . . ]

Slide 18

Slide 18 text

Инструменты метапрограммирования

Slide 19

Slide 19 text

MergeConf’22 Лексическая область видимости 19 class User end первая область видимости вторая область видимости • секция кода внутри синтаксической структуры программы; • не принимает во внимание структуру файлов, модулей или наследование.

Slide 20

Slide 20 text

MergeConf’22 Обычное объявление метода class User def full_name [f i rst_name, last_name].compact.join(" ") end end 20

Slide 21

Slide 21 text

MergeConf’22 Объявление метода класса с помощью префикса class User def self.admins where(is_admin: true) end end 21 def+pre fi x понимает, что self — это User и добавляет метод в метакласс

Slide 22

Slide 22 text

MergeConf’22 class User class < < self def admins where(is_admin: true) end end end 22 объявляем новую область видимости метод попадает в метакласс Объявление метода класса внутри новой лексической области видимости

Slide 23

Slide 23 text

MergeConf’22 Объявление метода на объекте class User end user = User.new def user.full_name [f i rst_name, last_name].compact.join(" ") end 23 метод попадает в метакласс

Slide 24

Slide 24 text

MergeConf’22 class User end user = User.new class < < user def full_name [f i rst_name, last_name].compact.join(" ") end end 24 Объявление метода на объекте внутри новой лексической области видимости объявляем новую область видимости

Slide 25

Slide 25 text

MergeConf’22 define_method 25 class User def i ne_method(:full_name) do [f i rst_name, last_name].compact.join(" ") end end

Slide 26

Slide 26 text

MergeConf’22 eval: выполнение кода из строки 26 eval "2 + 2"

Slide 27

Slide 27 text

MergeConf’22 27 def build_closure(x, y) binding end eval "x + y", build_closure(1, 2) binding: замыкание без функции

Slide 28

Slide 28 text

MergeConf’22 binding: замыкание без функции 28 User = Struct.new(:f i rst_name, :last_name) user = User.new('John', 'Doe') def full_name [f i rst_name, last_name].join(' ') end m = method(:full_name) m.class # = > # um = m.unbind um.class # = > UnboundMethod um.bind_call(user) # = > "John Doe"

Slide 29

Slide 29 text

MergeConf’22 instance_eval: меняем получателя 29 class User; end user = User.new user.instance_eval do [f i rst_name, last_name].compact.join(' ') end

Slide 30

Slide 30 text

MergeConf’22 Как доступиться до переменных? • instance_variables • instance_variable_set • instance_variable_get 30

Slide 31

Slide 31 text

Демо: роутер «как в рельсах»

Slide 32

Slide 32 text

MergeConf’22 Цель 1: делаем что работало 32 router = Router.draw do |builder| builder.get '/users', controller: :users, action: :index builder.post '/users', controller: :users, action: :create end puts router.route('GET /users') # = > {:controller= > : users, :action= > : index} puts router.route('POST /users') # = > {:controller= > : users, :action= > : create} puts router.route('GET /orders') # = > NOT_FOUND * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-04_builder_simple-rb

Slide 33

Slide 33 text

MergeConf’22 Router 33 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-04_builder_simple-rb class Router NOT_FOUND = 'NOT_FOUND' def self.draw builder = Builder.new yield(builder) new(builder) end def initialize(builder) @builder = builder end def route(method_and_path) method, path = method_and_path.split(' ') @builder.routes[[method.downcase, path]] | | NOT_FOUND end end

Slide 34

Slide 34 text

MergeConf’22 Builder 34 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-04_builder_simple-rb class Builder def get(path, * * args) routes[['get', path]] = args end def post(path, * * args) routes[['post', path]] = args end def routes @routes | | = {} end end

Slide 35

Slide 35 text

MergeConf’22 Цель 2: блок без аргумента 35 router = Router.draw do get '/users', controller: :users, action: :index post '/users', controller: :users, action: :create end puts router.route('GET /users') # = > {:controller= > : users, :action= > : index} puts router.route('POST /users') # = > {:controller= > : users, :action= > : create} puts router.route('GET /orders') # = > NOT_FOUND * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-05_builder_instance_eval-rb

Slide 36

Slide 36 text

MergeConf’22 Router v2: instance_eval 36 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-05_builder_instance_eval-rb class Router NOT_FOUND = 'NOT_FOUND' def self.draw(&block) new.tap { |router| router.instance_eval(&block) } end def route(method_and_path) method, path = method_and_path.split(' ') routes[[method.downcase, path]] | | NOT_FOUND end def get(path, * * args) routes[['get', path]] = args end def post(path, * * args) routes[['post', path]] = args end def routes @routes | | = {} end end блок будет выполнен в контексте инстанса Router все эти методы доступны внутри блока, переданного instance_eval

Slide 37

Slide 37 text

MergeConf’22 Router v3: генерируем методы 37 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-06_builder_generate_methods-rb class Router class < < self def draw(&block) new.tap { |router| router.instance_eval(&block) } end def generate_route_methods(*method_names) # . . . end end generate_route_methods :get, :post, :patch, :delete NOT_FOUND = 'NOT_FOUND' def route(method_and_path) method, path = method_and_path.split(' ') routes[[method.downcase, path]] | | NOT_FOUND end def routes @routes | | = {} end end

Slide 38

Slide 38 text

MergeConf’22 Router v3: генерируем методы 38 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-06_builder_generate_methods-rb class Router class < < self # . . . def generate_route_methods(*method_names) method_names.each do |method_name| def i ne_method(method_name) do |path, * * args| routes[[method_name, path]] = args end end end end generate_route_methods :get, :post, :patch, :delete # . . . end

Slide 39

Slide 39 text

Демо: естественный язык

Slide 40

Slide 40 text

MergeConf’22 dmitrytsepelev.dev/natural-language-programming-with-ruby 40 * https://gist.github.com/DmitryTsepelev/01702a27e86dd774d44998c3a3894dce VM.run(lang) do route from london to glasgow takes 22 route from paris to prague takes 12 how long will it take to get from london to glasgow end

Slide 41

Slide 41 text

MergeConf’22 Еще раз • в Ruby все является объектом и ведет себя схожим образом; • метапрограммирование — код, который пишет код; • include, prepend и extend помогают добавлять методы в контекст; • статические методы живут в метаклассе; • классы, модули и методы могут добавляться динамически. 41

Slide 42

Slide 42 text

@dmitrytsepelev Спасибо! Вопросы? 🌎 dmitrytsepelev.dev 42