method_missing
и компания)
define_method
eval
"Premature optimization is evil."
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
GC
- интерфейс към mark-and-sweep garbage collector-а на Ruby
GC.stop
спира събирането на неизползвани обектиGC.methods - Object.methods # [:start, :enable, :disable, :stress, :stress=, :count, :stat]
GC.constants # [:Profiler]
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.’
method_missing
.
Object
и хвърля NoMethodError
.
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
.
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
Нека имаме този клас:
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
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, :ident
def initialize(table, ident)
@table = table
@ident = ident
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})"
end
def set(col, val)
Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@ident}"
end
def get(col)
Database.sql("SELECT #{col} FROM #{@table} WHERE id=#{@ident}")[0][0]
end
end
class Movie < Entity
def initialize(ident)
super("movies", ident)
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
Метапрограмирането е писането на код, което управлява конструкциите на езика по време на изпълнение
Доста просто:
class MyClass
def my_method
@v = 1
end
end
obj = MyClass.new
obj.my_method
but_what_is object
# 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
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:0x413e44c8 @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
.
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! :)"
С Class.new
може да създадете анонимен клас
anonymous = Class.new do
def answer
42
end
end
instance = anonymous.new
instance.answer # 42
anonymous # #<Class:0x42686108>
Ако присвоите Class.new
на константа, стават магии
first = Class.new {}
SECOND = Class.new {}
first # #<Class:0x4230a1a0>
SECOND # SECOND
Името на класа се променя. Любопитно.
Прави същото като Class.new
, ама с модул
eval(text)
изпълнява код в низ
things = []
eval 'things << 42'
things # [42]
x = 1_024
vars = binding
vars # #<Binding:0x4251e1a8>
vars.eval('x') # 1024
x = 1_024
def foo
y = 42
binding
end
vars = foo
vars.eval('y') # 42
vars.eval('x') # error: NameError
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