13. Интроспекция и метапрограмиране, част 3

13. Интроспекция и метапрограмиране, част 3

13. Интроспекция и метапрограмиране, част 3

23 ноември 2016

Днес

Първи тест

Пета задача

Въпрос 1

Кажете всичко, което знаете за instance променливите.

  • Пазят се в обект
  • Достъпни са в наследници
  • Не са директно достъпни извън обекта
  • Трябва да се ползват методи за достъп (напр. attr_accessor)
  • Могат да се достъпят "заобиколно" с instance_variable_get и компания

Въпрос 2

Кой е класът на {}? На Integer? На Class?

Кой е родителят на String? На Object? На Class?

{}.class == Hash       # => true
Integer.class == Class # => true
Class.class == Class   # => true

String < Object        # => true
Object < BasicObject   # => true
Class < Module         # => true

Въпрос 3

Какво прави instance_eval? Ами instance_exec?

Изпълнява блока с променен self. instance_exec е същото, но може да подава аргументи на блока си.

Въпрос 4

Можем ли да направим така, че вместо изключения, несъществуващи локални променливи или методи да резултират в nil? Как?

foo          # => nil
foo.bar.baz  # => nil

Можем, като предефинираме метода method_missing в BasicObject.

class BasicObject
  def method_missing(*) end
end

instance_eval

припомняне

some_list = []

some_list.instance_eval do
  push 5
  push 'foo'
  push :bar
  push [:another, :list]

  size # => 4
end

some_list # => [5, "foo", :bar, [:another, :list]]

Текущ клас

Текущ клас

пример

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

Module#class_eval

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#module_eval е синоним на Module#class_eval.

Въпроси дотук?

Очакват ни define_method, чистото зло – eval и binding.

Три сродни метода

Module#define_method

class Something
  define_method :foo do |arg|
    "!#{arg}! :)"
  end
end

Something.new.foo('a') # => "!a! :)"

Module#define_method (2)

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! :)"

Module#define_singleton_method

Като `define_method`, но прави класов метод, не инстанционен.

class Something
  define_singleton_method :foo do |arg|
    "!#{arg}! :)"
  end
end

Something.foo('a') # => "!a! :)"
Something.new.foo('a') # => error: NoMethodError

eval

eval(text) изпълнява код в низ

things = []
eval 'things << 42'
things    # => [42]

Binding

Kernel#binding

x = 1_024

vars = binding

vars           # => #<Binding:0x002b3a00685848>
vars.eval('x') # => 1024

Kernel#binding (2)

x = 1_024

def foo
  y = 42
  binding
end

vars = foo
vars.eval('y') # 42
vars.eval('x') # error: NameError

Binding

други методи

Binding

вложеност

Scope gates

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 gates

заобикаляне

Scope gate-овете могат да се заобиколят с define_method, Class.new и Module.new.

Scope gates

define_method

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

Scope gates

Class.new

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

Scope gates

Module.new

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

Обърнете внимание, че тук Other се дефинира в root namespace-а.

def object.method

Може да (пре)дефинирате метод в конкретен обект.

things = [22, :f, 'Sofia']

def things.size
  -5
end

def things.asl
  "#{self[0]}/#{self[1]}/#{self[2]}"
end

things        # => [22, :f, "Sofia"]
things.size   # => -5
things.length # => 3
things.asl    # => "22/f/Sofia"

[].asl        # => error: NoMethodError
[].size       # => 0

Singleton класове

обяснението на предишния слайд

Singleton класове

визуализация

Object#singleton_class

Собственият клас е достъпен чрез #singleton_class

things = []

def things.answer
  42
end

things.singleton_class
# => #<Class:#<Array:0x002b0e4453ef58>>
things.singleton_class.instance_methods(false)
# => [:answer]
[].singleton_class.instance_methods(false)
# => []

Symbol и Integer

...и техните singleton класове

Целите числа и символите нямат singleton класове. Това е заради специалното им вътрешно представяне в Ruby.

1_000.singleton_class # => error: TypeError
:blah.singleton_class # => error: TypeError

class << thing

алтернативен синтаксис

Можете да отворите собствения клас на обект с class <<

things = [22, :f, 'Sofia']

class << things
  def size
    -5
  end

  def asl
    "#{self[0]}/#{self[1]}/#{self[2]}"
  end
end

things.asl  # => "22/f/Sofia"
things.size # => -5

super и singleton класове

Оригиналният метод е достъпен чрез super.

super и singleton класове (2)

things = [22, :f, 'Sofia']

class << things
  def each
    super
    yield :P
  end
end

aggregated = []
things.each do |thing|
  aggregated << thing
end

aggregated # => [22, :f, "Sofia", :P]

super и singleton класове

OMG момент

Някой има ли идея защо super работи?

things = []

def things.answer
  42
end

things.singleton_class # => #<Class:#<Array:0x002b38b23a1e00>>
things.singleton_class.superclass # => Array

Да, singleton класът е наследник на класа на обекта.

superclass и singleton класове

визуализация

Въпроси дотук?

Ще продължим с преглед отвътре на "класовите" методи.

Класови методи

Вероятно помните, че класови методи могат да се дефинират така:

class Something
  def Something.foo() 42 end
  def self.bar() 42 end

  class << self
    def qux() 42 end
  end
end

Класови методи

...всъщност

Класовите методи се пазят в собствения клас на класа

class Something
  def self.answer() 42 end
end

Something.singleton_class # => #<Class:Something>
Something.singleton_class.instance_methods(false) # => [:answer]

Класови методи

визуализация

Класови методи

instance_eval върху класове

class Something; end

Something.instance_eval do
  def say_something() 'something' end
end

Something.say_something # => "something"
Something.new.say_something # => error: NoMethodError

define_singleton_method

примерна имплементация

class Module
  def define_singleton_method(*args, &block)
    singleton_class.instance_eval do
      define_method(*args, &block)
    end
  end
end

Класови методи

class_eval

class Something; end

Something.class_eval do
  def say_something() 'something' end
end

Something.say_something # => error: NoMethodError
Something.new.say_something # => "something"

class_eval vs define_singleton_method

extend

...върху клас

Помните ли extend?

module Knowledge
  def answer() 42 end
end

class Something
  extend Knowledge
end

Something.answer # => 42

extend

...върху не-клас

module Knowledge
  def answer() 42 end
end

text = "fourty-two"
text.extend Knowledge

text.answer # => 42

Сещате ли се как може да се имплементира?

extend

...с class <<

module Knowledge
  def answer() 42 end
end

class Something; end

class << Something
  include Knowledge
end

Something.answer # => 42

extend

...чрез instance_eval и singleton клас

module Knowledge
  def answer() 42 end
end

class Something; end

Something.singleton_class.instance_eval { include Knowledge }

Something.answer # => 42

extend

...чрез include

module Knowledge
  def answer() 42 end
end

class Something; end

Something.singleton_class.include Knowledge

Something.answer # => 42

Изводи:

Въпроси дотук?

Ще продължим да дълбаем в класовите методи при наследяване.

Класови методи и наследяване

Класовите методи на родителя са достъпни в класовите методи на наследника:

class Something
  def self.answer() 42 end
end

class Other < Something
  def self.better_answer() answer * 2 end
end

Other.better_answer # => 84

Класови методи и наследяване

друг OMG момент

Собственият клас на наследника наследява собствения клас на родителя:

class Something; end
class Other < Something; end

Something.singleton_class        # => #<Class:Something>
Other.singleton_class.superclass # => #<Class:Something>

Something.singleton_class == Other.singleton_class.superclass # => true

Класови методи и наследяване

визуализация

Класови методи и наследяване

takeaway

Singleton класът на суперкласа е суперкласът на singleton класа.

Класови методи и наследяване

Класови методи и наследяване

ancestors

class Foo
end

Foo.singleton_class.ancestors
# => [#<Class:Foo>,
#     #<Class:Object>,
#     #<Class:BasicObject>,
#     Class,
#     Module,
#     Object,
#     PP::ObjectMixin,
#     Kernel,
#     BasicObject]

Класови методи и наследяване

DSL-и

class Cow
  def self.moo(say)
    puts "#{say} moo"
  end
end

class Foo < Cow
  moo 'Hello'
end

Ancestor chains

Няколко примера за ancestor chains

Относно търсенето на (инстанционни) методи на обектите от тип String:

''.singleton_class.ancestors
# => [#<Class:#<String:0x007f8788e51bd8>>,
#     String, Comparable, Object, Kernel, BasicObject]

''.class.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]

String.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]

Ancestor chains

за "класови" методи

По отношение на "класовите" методи, викани върхy String:

String.singleton_class.ancestors
# => [#<Class:String>, #<Class:Object>, #<Class:BasicObject>,
#     Class, Module, Object, Kernel, BasicObject]

String.class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]

Class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]

Grand Unified Theory

  1. Има само един вид обекти - били те обикновени или модули
  2. Има само един вид модули - били те обикновени или клас
  3. Има само един вид методи - живеят в модули, които често са класове
  4. Всеки обект има "реален клас" - бил той обикновен клас или собствен клас
  5. Всеки клас има точно един суперклас - с изключение на BasicObject
  6. Суперкласът на singleton класа на обект е класът на обекта. Суперкласът на singleton класа на клас е singleton класът на родителя на класа.
  7. При извикване на метод, Ruby взема "реалния клас" и търси в неговия ancestor chain

Въпроси