07. Класовете като обекти. Symbol#to_proc. Case. Замразяване

07. Класовете като обекти. Symbol#to_proc. Case. Замразяване

07. Класовете като обекти. Symbol#to_proc. Case. Замразяване

2 ноември 2016

Днес

Но първо...

Малко въпроси

Въпрос 1

Какво правят alias и alias_method? Каква е разликата между двете?

  • Копират метод
  • alias е ключова дума, alias_method е метод
  • Единствено alias_method може да се използва с променливи

Въпрос 2

Какъв ще е резултатът от кода по-долу? Защо?

answer = 42

universe = proc do
  def say_answer
    puts "The answer is: #{answer}"
  end

  say_answer
end
  • Нищо няма да стане, понеже не викаме Procuniverse (trolololo)
  • При извикване щяхме да получим грешка, понеже answer е недефинирано в say_answer
  • Причината е, че def, class и module са scope gate-ове
  • Дефиницията на блок не е scope gate!

Въпрос 3

Какво ще изведе на екрана следният код:

FOOD = 'muffin'
module Cake
  FOOD = 'fruit'
end
module Dessert
  FOOD = ::Cake
  module Cake
    FOOD = 'chocolate'
  end
end
puts FOOD
module Dessert
  puts Cake::FOOD
  module Cake
    puts Cake::FOOD
    puts ::Cake::FOOD
    puts Dessert::FOOD
  end
  puts FOOD
end
puts Dessert::FOOD::FOOD

Въпрос 3

Отговорът

FOOD = 'muffin'
module Cake
  FOOD = 'fruit'
end
module Dessert
  FOOD = ::Cake
  module Cake
    FOOD = 'chocolate'
  end
end
puts FOOD                #=> muffin
module Dessert
  puts Cake::FOOD        #=> chocolate
  module Cake
    puts Cake::FOOD      #=> chocolate
    puts ::Cake::FOOD    #=> fruit
    puts Dessert::FOOD   #=> Cake
  end
  puts FOOD              #=> Cake
end
puts Dessert::FOOD::FOOD #=> fruit

Въпрос 4

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

foo, (bar,), (larodi, *qux, answer), *others = 1, [2, 3], [1, 2, 3, 42]
  • foo #=> 1
  • bar #=> 2
  • larodi #=> 1
  • qux #=> [2, 3]
  • answer #=> 42
  • others #=> []

Root scope на константи - Object

Таблици с константи

Таблици с константи (2)

Можете да видите тази "таблица" през constants:

module MyLibrary
  class Object
  end
end

MyLibrary.constants         # => [:Object]
MyLibrary::Object == Object # => false

Текущ scope при търсене на константи

class Foo::Bar
  # In the Bar scope, but not in the Foo scope
end

Текущ scope при търсене на константи (2)

В този пример:

class Foo::Bar
  Larodi
end

Търсене на константи

module A::B
  module C::D
    Foo # Where does Ruby look for Foo?
  end
end
  1. В таблицата на D
  2. В таблицата на B
  3. В root scope-а (т.е. в таблицата на Object)

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

Задайте ги сега!

Symbol#to_proc

Следните два реда са (почти) еквивалентни:

name = proc { |object| object.name }
name = :name.to_proc

Когато подавате блок на метод с &block, Ruby извиква #to_proc, ако block не е ламбда или proc.

Съответно, следните два реда са еквивалентни

%w(foo plugh larodi).map { |s| s.length } # => [3, 5, 6]
%w(foo plugh larodi).map(&:length)        # => [3, 5, 6]

Symbol#to_proc

с повече от един аргумент

Всъщност, малко по-сложно е:

block = proc { |obj, *args| obj.method_name *args }
block = :method_name.to_proc

Това значи, че може да направите така:

[{a: 1}, {b: 2}, {c: 3}].reduce { |a, b| a.merge b } # => {:a=>1, :b=>2, :c=>3}
[{a: 1}, {b: 2}, {c: 3}].reduce(&:merge)             # => {:a=>1, :b=>2, :c=>3}

Или дори:

[1, 2, 3, 4].reduce { |sum, b| sum + b } # => 10
[1, 2, 3, 4].reduce(&:+)                 # => 10

Symbol#to_proc

Употреба

['Foo', :bar, 3].map(&:to_s).map(&:upcase)

Symbol#to_proc

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

class Symbol
  def to_proc
    # ...?
  end
end

Object#send

3.send :+, 4                # => 7
3.public_send :+, 4         # => 7
3.send :puts, 'Ruby magic!' # Ruby magic!
3.public_send :puts, 'wow'  # => error: NoMethodError

Symbol#to_proc

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

class Symbol
  def to_proc
    proc { |object, *args| object.public_send self, *args }
  end
end

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

???

Класовете като обекти

Класовете като обекти (2)

Долното работи:

a_hash_class = Hash
a_hash_class.new # => {}

Класовете като обекти (3)

anonymous_class = Class.new
anonymous_class.new # => #<#<Class:0x002b4f24590b20>:0x002b4f24590aa8>
Class.new.new       # => #<#<Class:0x002b4f24590530>:0x002b4f245904e0>
Module.new          # => #<Module:0x002b4f245900f8>

Класовете като обекти (4)

Можем да подадем блок на Class.new:

duck = Class.new do
  def quack_to(creature_name)
    "Quack, #{creature_name}, quack!"
  end
end

duck.new.quack_to("swan") # => "Quack, swan, quack!"

Обектите тип "клас" и константи

class Person
end

Класове и константи

Когато присвоим анонимен клас на константа, попадаме в специален случай:

Person = Class.new
Class.new           # => #<Class:0x002ba7326dbe10>
Person = Class.new  # => Person

Module.new          # => #<Module:0x002ba7326db578>
Larodi = Module.new # => Larodi

Въпроси?

?!?

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

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

преговор

Неканоничният и праволинеен начин за дефиниране на класов метод:

module Snake
  class Level
    def Level.load(level_data)
      # Code
    end
  end
end

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

втори начин

Този начин е еквивалентен на предишния слайд:

module Snake
  class Level
    def self.load(level_data)
      # Code
    end
  end
end

Това работи, понеже:

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

трети начин

Използва се при нужда да се дефинират няколко класови метода:

module Snake
  class Level
    class << self
      def load(level_data)
        # Code
      end
    end
  end
end

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

конвенции

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

Преди да продължим със следващата тема.

case

В Ruby има "switch". Казва се case.

def quote(name)
  case name
  when 'Yoda'
    puts 'Do or do not. There is no try.'
  when 'Darth Vader'
    puts 'The Force is strong with this one.'
  when 'R2-D2'
    puts 'Beep. Beep. Beep.'
  else
    puts 'Dunno what to say'
  end
end

Забележете индентацията.

case

особености

case

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

case operation
when :& then puts 'And?'
when :| then puts 'Or...'
when :! then puts 'Not!'
else         puts 'WAT?'
end

case

връщана стойност

На какво ще се оцени следният код?

case 'Wat?'
when 'watnot' then puts "I'm on a horse."
end

Ако няма else и никой when не match-не, се връща nil.

case

стойности

case не сравнява с ==. Може да напишете следното:

def qualify(age)
  case age
  when 0..12
    'still very young'
  when 13..19
    'a teenager! oh no!'
  when 33
    'the age of jesus'
  when 90..200
    'wow. that is old!'
  else
    'not very interesting'
  end
end

case

Object#===

case сравнява с ===. Няколко класа го имплементират:

case

Class#===

def qualify(thing)
  case thing
  when Integer then 'this is a number'
  when String  then 'it is a string'
  when Array   then thing.map { |item| qualify item }
  else 'huh?'
  end
end

case

Range#===

case hours_of_sleep
when 8..10 then 'I feel fine.'
when 6...8 then 'I am a little sleepy.'
when 0..3  then 'OUT OF MY WAY! I HAVE PLACES TO BE AND PEOPLE TO SEE!'
end

case

Regexp#===

def parse_date(date_string)
  case date_string
  when /(\d{4})-(\d\d)-(\d\d)/
    Date.new $1.to_i, $2.to_i, $3.to_i
  when /(\d\d)\/(\d\d)\/(\d{4})/
    Date.new $3.to_i, $1.to_i, $2.to_i
  end
end

case

when с няколко стойности

Ruby позволява да обедините проверката за няколко възможни стойности в един when, дори да не са от един и същи тип.

case number
when (42+0i)  then 'This is too complex for me!'
when 42, 42.0 then 'This is more like it!'
end

case

с обикновени условия

thing = 42
case
when thing == 1 then 1
else 'no_idea'
end

Въпроси по case

Сега е моментът.

Замразяване на обекти в Ruby

Замразяване на обекти

module Entities
  ENTITIES = {
    '&' => '&amp;',
    '"' => '&quot;',
    '<' => '&lt;',
    '>' => '&gt;',
  }.freeze

  ENTITY_PATTERN = /#{ENTITIES.keys.join('|')}/.freeze

  def escape(text)
    text.gsub ENTITY_PATTERN, ENTITIES
  end
end

Замразяване на низове

Тъй като низовете в Ruby са mutable, всяко срещане на низ = нов обект:

''.object_id                 # => 23775293466780
''.object_id                 # => 23775293466240
''.object_id == ''.object_id # => false

Замразяване на низове

пример

class HTTP
  attr_reader :method

  def post?
    method == 'POST' # New string object on each call
  end
end

Замразяване на низове

пример за workaround

class HTTP
  attr_reader :method
  METHOD_POST = 'POST'

  def post?
    method == METHOD_POST
  end
end

Това е досадно. Затова в Ruby 2.1 има и по-добро решение.

String#freeze

''.freeze.object_id                        # => 23589015467900
''.freeze.object_id                        # => 23589015467900
''.freeze.object_id == ''.freeze.object_id # => true

Синтаксис за замразяване на низове

Оптимизация при замразяване

работи само с литералния синтаксис

'text'.freeze.object_id                         # => 23534545930840
foo = 'text'
foo.freeze.object_id                            # => 23534545928760
foo.freeze.object_id == 'text'.freeze.object_id # => false

Замразяване на всички стрингове във файл

Замразяване на обекти

Размразяване на обекти

Arendelle's salvation

require 'fiddle'

class Object
  def unfreeze
    Fiddle::Pointer.new(object_id * 2)[1] &= ~(1 << 3)
  end
end

script = 'Do you want to build a castle of ice?'.freeze
script.frozen? # => true
script.unfreeze
script[/castle of ice/] = 'snowman'
script.frozen? # => false
script # => "Do you want to build a snowman?"

Въпроси