DSL
-и (domain specific languages)
Каква е разликата между include
и extend
?
module Foo
def bar() end
def baz() end
end
class Bucket
include Foo
extend Foo
end
include
прави методите на модула достъпни като инстанционни в получателя Bucket
, докато extend
ги прави достъпни като класови методи.
Можем ли, и ако да, как, да вземем списък с имената на всички константи, дефинирани в root scope-а?
Можем. Константите в root scope-а са реално константите, дефинирани в Object
. Можем да ползваме метода за интроспекция Module#constants
, за да получим списък с имената на константите: Object.contstants
.
На какво ще се оцени кодът по-долу? Защо?
object_a = String
object_b = 'foo'
object_a.methods.size == object_b.methods.size # => ?
Резултатът ще е false
. Причината за това е, че object_a
е тип "клас" и отгoваря на едни методи, а object_b
е тип "низ" и отгваря на други методи. Object#methods
е интроспективен метод, връщащ списък с нещата, които можем да извикваме на получателя.
На какво ще се оцени кодът по-долу? Защо?
class Rectangle
attr_accessor :x, :y, :width, :height
def initialize(x, y) @x, @y = x, y end
end
rect = Rectangle.new(5, 10)
rect.width = 100
rect.instance_variables # => ?
Резултатът ще е [:@x, :@y, :@width]
. Интроспективният метод Object#instance_variables
връща списък с имената на инстанционните променливи на дадения обект. Инстанционните променливи се появяват при първото им задаване на стойност. attr_*
методите вътрешно ползват инстанционни променливи.
Какво ще се изведе на екрана в резултат на изпълнението на следния код? Защо?
x = 42
defined?(x += 1)
puts x
На екрана ще се изведе 42
. Причината за това е, че изразът, подаден като аргумент на defined?
, няма да бъде изпълнен/оценен, тъй като defined?
е синтаксис и оценява "подадения" израз само синтактично.
defined?
връща или низ с описанието на израза, или nil
, ако това е несъществуваща локална променлива/метод.
Има ли начин да вземем подадения на метод в Ruby блок като обект, инстанция на класа Proc
, ако методът няма в сигнатурата си параметър от вида &block
?
Има. Ако в метода извикаме Proc.new
без аргументи и без блок, Proc.new
ще ни върне блока, асоцииран с извикването на обкръжаващия метод. Разбира се, ако няма такъв блок, ще се получи грешка.
Представете си, че сте открили, че някоя библиотека в Ruby програмата ви е monkey patch-нала Hash#reject
и това е довело до неочаквани бъгове във вашия код. Как може да откриете виновника?
Бихте могли да използвате интроспекцията Object#method(method_name)
по следния начин, някъде в програмата ви:
raise {}.method(:reject).source_location.inspect
Горното ще предизвика изключение, чиито текст ще изглежда така:
RuntimeError: ["/path/to/the/offending/gem.rb", 42]
Можем ли да извикваме с call
инстанции на UnboundMethod
? Защо?
Инстанциите на UnboundMethod
не могат да бъдат извиквани, тъй като са "откачени" от реален обект-получател.
Какво прави методът arity
на класа Method
? Кои други класове имат такъв публичен метод?
Method#arity
връща информация за броя и частично – за вида на позиционните аргументи на даден метод.
Такъв метод имат и инстанциите на класа Proc
, тоест – блокове и анонимни функции. Също така и класът UnboundMethod
.
method_missing
и компания)
define_method
eval
Module#method_added(method_name)
Module#method_removed(...)
module Foo
def self.method_added(name)
puts "A-ha! You added the #{name} method!"
end
end
module Foo
def bar
end
end # Извежда "A-ha! You added the bar method!"
Kernel::singleton_method_added
се вика, когато добавите класов метод в модул или клас
Class#inherited
се вика, когато вашият клас бъде наследен от друг клас
Bar.included
се вика, когато някой напише class Foo; include Bar; end
Bar.extend_object
се вика, когато някой напише class Foo; extend Bar; end
def self.some_hook_name(...) end
included
е може би най-често използваната; inherited
също се ползва понякогаGC
- интерфейс към mark-and-sweep garbage collector-а на Ruby
GC.stop
спира събирането на неизползвани обектиGC.methods - Object.methods # [:start, :enable, :disable, :stress, :stress=, :count, :stat, :latest_gc_info, :verify_internal_consistency]
GC.constants # [:INTERNAL_CONSTANTS, :Profiler, :OPTS]
GC::Profiler.enable
require 'active_support/ordered_hash'
puts GC::Profiler.result
Резултати:
GC 8 invokes. Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms) 1 0.706 2889840 16818080 420452 23.71699999999998809130
Kernel#set_trace_func(proc)
c-call
- извикване на C-метод
c-return
- връщане от C-метод
call
- извикване на Ruby метод
return
- връщане от Ruby метод
class
начало на дефиниция на клас или модул
end
край на горната дефиниция
line
изпълняване на код на ред Х
raise
възникнало изключениеtracer = proc do |event, file, line, id, binding, classname|
printf "%8s %s:%-2d %15s %15s\n", event, file, line, id, classname
end
set_trace_func tracer
class Foo
def bar
a, b = 1, 2
end
end
larodi = Foo.new
larodi.bar
c-return t.rb:5 set_trace_func Kernel line t.rb:7 c-call t.rb:7 inherited Class c-return t.rb:7 inherited Class class t.rb:7 line t.rb:8 c-call t.rb:8 method_added Module c-return t.rb:8 method_added Module end t.rb:11 (Продължава на следващия слайд...)
(Продължение от предишния слайд...) line t.rb:13 c-call t.rb:13 new Class c-call t.rb:13 initialize BasicObject c-return t.rb:13 initialize BasicObject c-return t.rb:13 new Class line t.rb:14 call t.rb:8 bar Foo line t.rb:9 bar Foo return t.rb:10 bar Foo
Kernel#trace_var
, за да следите за промени по глобални променливи
trace_var :$_ do |value|
puts "$_ is now #{value.inspect}"
end
$_ = "Ruby"
$_ = ' > Python'
Извежда следното:
$_ is now "Ruby" $_ is now " > Python"
Метапрограмирането е писането на код, който пише друг код
meta- (also met- before a vowel or h)
combining form
1. denoting a change of position or condition : metamorphosis | metathesis.
2. denoting position behind, after, or beyond: : metacarpus.
3. denoting something of a higher or second-order kind : metalanguage | metonym.
4. Chemistry denoting substitution at two carbon atoms separated by one other in a benzene ring, e.g., in 1,3 positions : metadichlorobenzene. Compare with ortho- and para- 1 .
5. Chemistry denoting a compound formed by dehydration : metaphosphoric acid.
ORIGIN from Greek meta ‘with, across, or after.’
Нека имаме този клас:
class Student
attr_accessor :name, :age, :faculty_number
def initialize(**attributes)
attributes.each do |name, value|
send "#{name}=", value
end
end
end
average_joe = Student.new name: 'Joe', age: 33, faculty_number: '42042'
average_joe.name # "Joe"
average_joe.age # 33
average_joe.faculty_number # "42042"
Нека имаме списък с такива студенти:
students = [
Student.new(name: 'Asya', age: 6, faculty_number: '12345'),
Student.new(name: 'Stefan', age: 28, faculty_number: '666'),
Student.new(name: 'Tsanka', age: 12, faculty_number: '42042'),
Student.new(name: 'Sava', age: 3, faculty_number: '53453'),
]
И нека сме направили този monkey patch:
class Enumerator
def extract(&block)
each do |object|
block.call object.send(block.parameters.first.last)
end
end
end
Тогава, можем да направим това:
students.map.extract { |name| name } # ["Asya", "Stefan", "Tsanka", "Sava"]
students.map.extract { |age| age } # [6, 28, 12, 3]
students.each.extract do |faculty_number|
puts faculty_number # Prints out 12345, then 666, then 42042, then 53453
end
При извикване на метод bar
върху обект foo
в Ruby:
foo.bar(42)
foo.ancestors
, имащ метод bar
foo.ancestors
се изчерпи и не се намери метод bar
?
NoMethodError
, се случва още нещо
ancestors
още веднъж, извиквайки method_missing(:bar, 42)
BasicObject
и имплементацията му хвърля NoMethodError
class BasicObject
def method_missing(method_name, *arguments, &block)
message = "undefined local variable or method `#{method_name}' for #{inspect}:#{self.class}"
raise NoMethodError, message
end
end
ancestors
(обикновено в нашия клас)
super
class Hash
def method_missing(name, *args, &block)
args.empty? ? self[name] : super
end
end
things = {fish: 'Nemo', lion: 'Simba'}
things.fish # "Nemo"
things.lion # "Simba"
things.larodi # nil
things.foo(1) # error: NoMethodError
Има и коварни моменти:
class Hash
def method_missing(name, *arg, &block)
args.empty? ? self[name] : super
end
end
things = {lion: 'Simba'}
things.lion# ~> -:3: stack level too deep (SystemStackError)
arg
вместо args
.
args
всъщност е self.args
.
method_missing
и – хоп! Бездънна рекурсия.respond_to?
respond_to?
не работи за методите, на които "отговаряте" през method_missing
respond_to?
вика respond_to_missing?
, ако методът, за който питате, не е дефиниран
respond_to_missing?
, ако имате и method_missing
class Foo
def respond_to_missing?(symbol, include_private)
# Return true or false
end
end
class Foo
def respond_to_missing?(method_name, include_private)
puts "Looking for #{method_name}"
super
end
private
def bar() end
end
Foo.new.respond_to? :larodi # false и на екрана се извежда "Looking for larodi"
Foo.new.respond_to? :bar # false и на екрана се извежда "Looking for bar"
Foo.new.respond_to? :bar, true # true
module Unicode
def self.const_missing(name)
if name.to_s =~ /^U([0-9a-fA-F]{4,5}|10[0-9a-fA-F]{4})$/
codepoint = $1.to_i(16)
utf8 = [codepoint].pack('U')
utf8.freeze
const_set(name, utf8)
utf8
else
super
end
end
end
Unicode::U20AC # "€"
Unicode::U221E # "∞"
Unicode::Baba # error: NameError
class Entity
attr_reader :table, :id
def initialize(table, id)
@table = table
@id = id
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@id})"
end
def set(col, val)
Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@id}"
end
def get(col)
Database.sql("SELECT #{col} FROM #{@table} WHERE id=#{@id}")[0][0]
end
end
class Movie < Entity
def initialize(id)
super("movies", id)
end
def title
get("title")
end
def title=(value)
set("title", value)
end
def director
get("director")
end
def director=(value)
set("director", value)
end
end
Тук имаше повторение.
movies
с колона title
и клас Movie
с поле @title
"title"
се повтаря
title()
и title=()
някак повтаря двойката director()
и director=()
"movies"
изглежда повтаря, че класът се казва Movie
С малко метапрограмиране би могло да изглежда така:
class Movie < ActiveRecord::Base
end
activerecord
е съществуваща библиотека, част от Ruby on Rails
Movie
моделира таблица movies
, идентификаторът се пази в колона id
)
Метапрограмирането е писането на код, което управлява конструкциите на езика по време на изпълнение
Доста просто:
class MyClass
def my_method
@v = 1
end
end
obj = MyClass.new
obj.my_method
but_what_is obj
# Spoiler alert: there is no `but_what_is' method
class MyClass
def initialize
@a = 1
@b = 2
end
end
MyClass.new.instance_variables # [:@a, :@b]
class Person
def approximate_age
2011 - @birth_year
end
end
person = Person.new
person.instance_variables # []
person.instance_variable_set :@birth_year, 1989
person.approximate_age # 22
person.instance_variable_get :@birth_year # 1989
Можете да вземете класа на всеки обект с Object#class
.
"abc".class # String
"abc".class.class # Class
"abc".class.class.class # Class
String.instance_methods == "abc".methods # true
String.methods == "abc".methods # false
"abc".length # 3
String.length # error: NoMethodError
String.ancestors # [String, Comparable, Object, Kernel, BasicObject]
"abc".ancestors # error: NoMethodError
Можете да вземете родителския клас с Object#superclass
.
class A; end
class B < A; end
class C < B; end
C.superclass # B
C.superclass.superclass # A
C.superclass.superclass.superclass # Object
Class
е наследник на Module
Class.superclass == Module
Class < Module
Module#foo
за клас-макроси
private
, protected
, attr_accessor
, include
, extend
и т.н.
Module
или Class
Module
и Class
имат достъп до тези методиBasicObject
идва в Ruby 1.9 и е много опростена версия на Object
.
Подходящ е за method_missing
магарии
Object.instance_methods.count # 56
BasicObject.instance_methods.count # 8
m = BasicObject.instance_methods.join(', ')
m # "==, equal?, !, !=, instance_eval, instance_exec, __send__, __id__"
Което ни навежда на следващия въпрос - instance_eval
self
self
се ползва за полета (@foo
) и търсене на методи (bar()
).
instance_eval
променя self
в рамките на един блокclass Person
private
def greeting() "I am #{@name}" end
end
mityo = Person.new
mityo.instance_eval do
@name = 'Mityo'
greeting # "I am Mityo"
self # #<Person:0x4254639c @name="Mityo">
end
self # main
mityo.instance_variable_get :@name # "Mityo"
instance_exec
е като instance_eval
, но позволява да давате параметри на блока.
obj = Object.new
set = ->(value) { @x = value }
obj.instance_exec(42, &set)
obj.instance_variable_get :@x # 42
obj.instance_eval { @x } # 42
Това е смислено, когато блока се подава с &
. Иначе няма нужда.
self
), има и текущ клас
def
class
и module
го променятdef foo() end # Тук е Object
class Something
def bar() end # Тук е Something
class OrOther
def baz() end # Тук е Something::OrOther
end
end
class Something
def foo
def bar
6 * 9
end
bar - 12
end
end
something = Something.new
something.foo # 42
something.bar # 54
class_eval
променя self
и текущия клас
def monkey_patch_string
String.class_eval do
self # String
def answer
42
end
end
end
"abc".respond_to? :answer # false
monkey_patch_string
"abc".respond_to? :answer # true
Module#module_eval
е синоним на Module#class_eval
.
send
– извикване на метод, ако имате името му в променлива
method_missing
– "отговаряне" на несъществуващи методи
define_method
– динамично създаване на методиclass Something
define_method :foo do |arg|
"!#{arg}! :)"
end
end
Something.new.foo('a') # "!a! :)"
class Something
METASYNTACTIC = %w[foo bar baz]
METASYNTACTIC.each do |name|
define_method name do |arg|
"!#{arg}! :)"
end
end
end
Something.new.bar('a') # "!a! :)"
Something.new.baz('a') # "!a! :)"
eval(text)
изпълнява код в низ
things = []
eval 'things << 42'
things # [42]
eval
?
x = 1_024
vars = binding
vars # #<Binding:0x423b28b4>
vars.eval('x') # 1024
x = 1_024
def foo
y = 42
binding
end
vars = foo
vars.eval('y') # 42
vars.eval('x') # error: NameError
local_variable_[gs]et
module
, class
и def
секват binding-а
top_level = 1
module Something
in_module = 2
class Other
in_class = 3
def larodi
top_level # error: NameError
in_module # error: NameError
in_class # error: NameError
end
end
end
Something::Other.new.larodi
Scope gate-овете могат да се заобиколят с define_method
, Class.new
и Module.new
.
top_level = 1
module Something
in_module = 2
class Other
in_class = 3
define_method :larodi do
top_level # error: NameError
in_module # error: NameError
in_class # 3
end
end
end
Something::Other.new.larodi
top_level = 1
module Something
in_module = 2
Other = Class.new do
in_class = 3
define_method :larodi do
top_level # error: NameError
in_module # 2
in_class # 3
end
end
end
Something::Other.new.larodi
top_level = 1
Something = Module.new do
in_module = 2
Other = Class.new do
in_class = 3
define_method :larodi do
top_level # 1
in_module # 2
in_class # 3
end
end
end
Other.new.larodi
class Proxy < BasicObject
def initialize(obj)
@instance = obj
end
def method_missing(name, *args, &block)
$stdout.puts "Calling #{ name } with (#{ args.join(', ') })"
@instance.send(name, *args)
end
end
a = []
b = Proxy.new a
b.length # 0
some_collection.each(&block)
String#split(//)
vs. String#chars
– кое е по-ясно?