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

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

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

12 ноември 2014

Днес

Ориентировъчен план за следващите лекции

Преди това

новините

План за неделя, 16 ноември

традиционната планина

RubyMonk

отклонение

Въпрос 1

Какво прави caller?

Връща списък с низове, всеки ред от който представлява елемент от текущия (в мястото на извикването на caller) call stack. Нещо такова:

.../irb/ruby-lex.rb:232:in `catch'
.../irb/ruby-lex.rb:232:in `each_top_level_statement'
.../irb.rb:488:in `eval_input'
.../irb.rb:397:in `block in start'
.../irb.rb:396:in `catch'
.../irb.rb:396:in `start'
.../bin/irb:11:in `<main>'

Въпрос 2

Кои изключения обикновено е добра идея да хващаме и кои не?

  1. Обикновено хващаме непредвидими грешки, причинени от "околната среда" (най-често IO-грешки).
  2. Програмистки грешки, причинени от неправилна употреба на парче код, обикновено избягваме да хващаме.

Въпрос 3

За какво служат throw и catch в Ruby?

  • catch и throw се ползват за control flow; не служат за обработка на грешки
  • Единственото общо между тях и изключенията е, че и те bubble up-ват нагоре по call стека
  • Ползват се рядко

Въпрос 4

Какво правеше клас-макрото include?

module Bar
  def public_method() end
  private
  def private_method() end
end

class Foo
  include Bar
end

"Копира" методите, дефинирани в модула, със съответната им видимост (public, private, ...) като инстанционни методи в класа-получател.

include и extend

Помните Module#include, нали?

module Introductions
  def ahoy() "Ahoy my mate, I'm #{name}!" end
  def hi()   "Hey there, I'm #{name}!" end
end

class Person
  include Introductions
  attr_accessor :name

  def initialize(name) @name = name end
end

Person.new('Silver').ahoy # "Ahoy my mate, I'm Silver!"
Person.new('Silver').hi   # "Hey there, I'm Silver!"

include и extend (2)

Module#extend добавя методите на дадения модул като класови методи:

module Introductions
  def ahoy() "Ahoy my mate, I'm #{name}!" end
  def hi()   "Hey there, I'm #{name}!" end
end

class Person
  extend Introductions
end

Person.ahoy # "Ahoy my mate, I'm Person!"
Person.hi   # "Hey there, I'm Person!"

extend с модули

Аналогично на include, може да ползвате extend и в модули:

module Person
  extend Creation
  extend Traversing
  extend Reflection
end

extend с модули

Може дори да include-нете и да extend-нете с един и същи модул:

class Person
  include Introductions
  extend Introductions
end

Person.new('Why').hi # "Hey there, I'm Why!"
Person.hi            # "Hey there, I'm Person!"

extend self

Долното работи и се ползва понякога:

module UrlHelpers
  extend self

  def encode(url)
  end

  def decode(url)
  end
end

UrlHelpers.encode('foo bar')
UrlHelpers.decode('foo%20bar')

Въпроси по extend?

Преди да продължим с интроспекция.

Интроспекция

Интроспекция

Code smell

Интроспекция на ОО йерархията

Разглеждане на обекти

методите им

'foo'.methods.size  # 165
String.methods.size # 101

Разглеждане на обекти

Методите им

Методи на обекти

Пример

class Ruby
  def self.author() 'Matz' end

  def version() '2.1.0' end
  def repository() 'Git' end
  def implementation() 'MRI C' end

  protected :repository
  private :implementation
end

Ruby.public_instance_methods     # [:version, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :gem, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
Ruby.protected_instance_methods  # [:repository]
Ruby.private_instance_methods    # [:implementation, :Digest, :timeout, :initialize_copy, :initialize_dup, :initialize_clone, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :respond_to_missing?, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :trap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :require, :require_relative, :autoload, :autoload?, :proc, :lambda, :binding, :caller, :caller_locations, :Rational, :Complex, :set_trace_func, :gem_original_require, :Pathname, :URI, :rubygems_require, :initialize, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :method_missing]
Ruby.singleton_methods           # [:author]

Методи на обекти

Инстанционни променливи

Инстанционни променливи

Пример

class Foo
  def initialize
    @baba = 42
  end

  def touch
    @touched = true
  end
end

foo = Foo.new

foo.instance_variables # [:@baba]
foo.touch
foo.instance_variables # [:@baba, :@touched]

Инстанционни променливи

Пример

class Foo
  def initialize
    @baba = 42
  end
end

foo = Foo.new

foo.instance_variable_defined? :@baba # true

foo.send(:remove_instance_variable, :@baba)
foo.instance_variable_defined? :@baba # false

Константи

Константи

Константи

Regexp.constants          # [:IGNORECASE, :EXTENDED, :MULTILINE, :FIXEDENCODING, :NOENCODING]
Object.constants.take(5)  # [:Object, :Module, :Class, :BasicObject, :Kernel]
Object.constants.size     # 145

Константи

не правете така

String # String
Object.const_set(:String, Fixnum)
String # Fixnum

Глобални променливи

Kernel#global_variables ще ви върне списък с всички глобални променливи,
дефинирани към момента. Например:

puts global_variables.map { |var| var.to_s.ljust(16) }
                     .each_slice(4)
                     .map { |vars_per_line| vars_per_line.join(' ') }
                     .join("\n")

Глобални променливи

$;               $-F              $@               $!
$SAFE            $~               $&               $`
$'               $+               $=               $KCODE
$-K              $,               $/               $-0
$\               $_               $stdin           $stdout
$stderr          $>               $<               $.
$FILENAME        $-i              $*               $?
$$               $:               $-I              $LOAD_PATH
$"               $LOADED_FEATURES $VERBOSE         $-v
$-w              $-W              $DEBUG           $-d
$0               $PROGRAM_NAME    $-p              $-l
$-a              $binding         $1               $2
$3               $4               $5               $6
$7               $8               $9

Локални променливи

foo          = :bar
die_antwoord = 42

local_variables # [:foo, :die_antwoord, :_xmp_1421262103_16916_232677]

defined?

defined?

unless defined? cache
  cache = true
end

defined?

gotcha

x = 42 unless defined? x
x # nil

defined?

defined? 1             # "expression"
defined? foo           # nil
defined? puts          # "method"
defined? String        # "constant"
defined? $&            # nil
defined? $_            # "global-variable"
defined? Math::PI      # "constant"
defined? answer = 42   # "assignment"
defined? 42.abs        # "method"

block_given?

Можете да ползвате Kernel#block_given?, за да проверите дали
някой ви е подал блок, например:

def block_or_no_block
  if block_given?
    'We got a block, go this way...'
  else
    'No block man, go that way...'
  end
end

block_or_no_block     # "No block man, go that way..."
block_or_no_block {}  # "We got a block, go this way..."

Proc.new и yield

Proc.new

Пример с Proc.new:

def foo
  Proc.new if block_given?
end

foo         # nil
foo {}      # #<Proc:0x426af0e4@-:6>

proc = foo { "hello" }
proc.call   # "hello"

ObjectSpace

ObjectSpace.each_object

ObjectSpace.each_object.count          # 26901
ObjectSpace.each_object(String).count  # 18375
ObjectSpace.each_object(Float).to_a    # [NaN, Infinity, 2.220446049250313e-16, 1.7976931348623157e+308, 2.2250738585072014e-308, 2.718281828459045, 3.141592653589793, -Infinity, Infinity, 0.0, 100.0, 100.0, 100000.0, 100.0, 10000000.0, 100.0, 1000.0, 1000.0, 1000.0, 60000.0, 3600000.0, 0.0]
ObjectSpace.each_object(Class).count   # 681

ObjectSpace.each_object(Class) do |klass|
  # Do something with klass
end

ObjectSpace.count_objects

Дава проста статистика на бройките обекти в паметта по типове. Връща хеш. Например:

puts ObjectSpace.count_objects
      .map { |type, count| "#{type}:".ljust(10) + count.to_s.rjust(7) }
      .each_slice(3)
      .map { |counts_per_row| counts_per_row.join('    ') }
      .join("\n")

ObjectSpace.count_objects

Резултати

TOTAL:     419019    FREE:      289687    T_OBJECT:   12958
T_CLASS:      880    T_MODULE:      34    T_FLOAT:        8
T_STRING:   85708    T_REGEXP:     175    T_ARRAY:    21544
T_HASH:       707    T_STRUCT:       1    T_BIGNUM:       6
T_FILE:         7    T_DATA:      2188    T_MATCH:      643
T_COMPLEX:      1    T_NODE:      4436    T_ICLASS:      36

ObjectSpace._id2ref

id = 'larodi'.object_id   # 550842600
ObjectSpace._id2ref(id)   # "larodi"

Деструктори в Ruby?

ObjectSpace.define_finalizer

foo = 'Memory is plentiful!'
ObjectSpace.define_finalizer foo, proc { puts 'foo is gone' }
ObjectSpace.garbage_collect

foo = nil
ObjectSpace.garbage_collect

# Тук proc-ът от по-горе бива извикан
# и на STDOUT се извежда "foo is gone"

Object#method(method_name)

class Person
  def name
    'Matz'
  end
end

Person.new.method(:name) # #<Method: Person#name>

Класът Method

Или какво може да правите с обектите му

Method#call

class Person
  attr_accessor :name
end

someone = Person.new
someone.name = 'Murakami Haruki'

name_method = someone.method(:name)
name_method.call  # "Murakami Haruki"

Method#arity и #parameters

def foo(x, y, z); end

method(:foo).arity      # 3
method(:foo).parameters # [[:req, :x], [:req, :y], [:req, :z]]

def bar(x, y, z = 1); end

method(:bar).arity      # -3
method(:bar).parameters # [[:req, :x], [:req, :y], [:opt, :z]]

Proc#arity

Proc.new { |x| }.arity      # 1
lambda { |x, y| }.arity     # 2

Method#source_location

require 'set'
Set.new.method(:to_a).source_location # ["/Users/.../ruby-1.9.3/lib/ruby/1.9.1/set.rb", 144]

class Set; def to_a; super; end; end
Set.new.method(:to_a).source_location # ["-", 4]

Необвързани методи

Необвързани методи

class Person
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

stefan = Person.new 'Stefan'
mitio  = Person.new 'Mitio'

stefan.name                           # "Stefan"

stefans_name = stefan.method(:name)
stefans_name.call                     # "Stefan"
stefans_name.unbind.bind(mitio).call  # "Mitio"

Интроспекция

Следва продължение...

Трета задача

Цели

Трета задача

skeptic

Трета задача

Въпроси

Трета задача

File#serialize

def serialize
  "#{data_type}:#{@data}"
end

Трета задача

File.parse

def self.parse(string)
  File.new parse_data_with_type(*string.split(':', 2))
end

private

def self.parse_data_with_type(type, data)
  case type
    when 'nil'    then nil
    when 'string' then data
    when 'symbol' then data.to_sym
    when 'number' then data.to_f
    else               data == 'true'
  end
end

Трета задача

Directory#serialize

def serialize
  files       = "#{@files.size}:#{serialize_entities(@files)}"
  directories = "#{@directories.size}:#{serialize_entities(@directories)}"

  "#{files}#{directories}"
end

Трета задача

Directory#serialize_entities

def serialize_entities(entities)
  entities.map do |name, entity|
    serialized_entity = entity.serialize

    "#{name}:#{serialized_entity.size}:#{serialized_entity}"
  end.join('')
end

Трета задача

Directory.parse

Да си дефинираме нов клас Parser със следния публичен интерфейс:

Трета задача

Parser#read_next_record

def read_next_record
  record, @data = @data.split(':', 2)
  record
end

Трета задача

Parser#read_next_entity

def read_next_entity
  size   = read_next_record.to_i
  entity = @data[0...size]

  @data = @data[size...@data.size]

  entity
end

Трета задача

Parser#each

def each
  array_size = read_next_record.to_i

  array_size.times do
    entity_name   = read_next_record
    entity_string = read_next_entity

    yield entity_name, entity_string
  end
end

Трета задача

Directory.parse

def self.parse(string_data)
  parser = Parser.new(string_data)

  files       = {}
  directories = {}

  parser.each { |name, entity| files[name]       = File.parse(entity)      }
  parser.each { |name, entity| directories[name] = Directory.parse(entity) }

  Directory.new(files, directories)
end

Трета задача

Изводи

Въпроси