06. Именовани пространства. Класови методи. Паралелно присвояване. Класове и константи

06. Именовани пространства. Класови методи. Паралелно присвояване. Класове и константи

06. Именовани пространства. Класови методи. Паралелно присвояване. Класове и константи

28 октомври 2015

Днес

Въпрос 1

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

class Something
  def name() 'baba' end
  alias relative name
  def name() 'dyado' end
end

p Something.new.relative

Прави копие на метода

Въпрос 2

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

alias_method е метод, докато alias е синтаксис

class Something
  1.upto(5).each do |index|
    alias_method "to_s_#{index}", :to_s
  end
end

Въпрос 3

Какво ще има в ancestors chain-a?

module Foo; end
module Bar; end
module Baz; end

module Qux
  include Foo
  include Bar
end

class Garply
  include Foo
  include Qux
end

class Waldo < Garply
  include Foo
  include Baz
  include Bar
end

Waldo.ancestors # =>

[Waldo, Baz, Garply, Qux, Bar, Foo, Object, Kernel, BasicObject]

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

Класовете и модулите могат да служат като именувани пространства.

module Useless
  class Thing
  end
end

class Grandfather
  class StraightRazor
  end
end

Useless::Thing.new             # => #<Useless::Thing:0x002b794d68a708>
Grandfather::StraightRazor.new # => #<Grandfather::StraightRazor:0x002b794d68a348>

Именувани пространства (2)

Ако се намирате в модул, няма нужда да ползвате пълния път до константите:

module Useless
  class Thing
  end

  Thing.new         # => #<Useless::Thing:0x002ba1fae11a00>
end

Useless::Thing.new  # => #<Useless::Thing:0x002ba1fae11528>
Thing.new           # => error: NameError

Търсене на променливи в Ruby

bacon = 2

def foo
  chunky = 10

  1.times do
    chunky       # 10
    chunky = 44
  end

  chunky         # 44
  bacon          # error: NameError
end

foo()

Правила за търсене на константи

Малък пример

PLACE = 'root'
module Outer
  PLACE = 'intermediate'
  module Inner
    PLACE = 'deep'
  end
end

PLACE               # => "root"
Outer::Inner::PLACE # => "deep"
module Outer
  module Inner
    PLACE           # => "deep"
    ::PLACE         # => "root"
  end
  PLACE             # => "intermediate"
  Inner::PLACE      # => "deep"
end

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

Може да дефинирате класови методи така:

class Something
  def Something.answer
    42
  end
end

Something.answer   # => 42

Класови методи (2)

Не може да ги викате неквалифицирано от инстанцията:

class Something
  def Something.answer
    42
  end

  def do_stuff
    answer             # => error: NameError
    Something.answer   # => 42
  end
end

thing = Something.new
thing.answer           # => error: NoMethodError
Something.answer       # => 42

thing.do_stuff

Класови методи (3)

Достъпни са в наследниците:

class Base
  def Base.answer() 42 end
end

class Derived < Base
  def Derived.say_answer
    answer         # => 42
    Base.answer    # => 42
  end
end

Derived.answer     # => 42
Base.answer        # => 42

Derived.say_answer

Класови методи (4)

Има и други начини за дефиниция на класови методи:

Класови методи (5)

Присвояване

Паралелно присвояване

прост пример

a, b = 1, 2
a              # => 1
b              # => 2

a, b = b, a
a              # => 2
b              # => 1

Има няколко различни случая, които ще разгледаме.

Паралелно присвояване

присвояване на една променлива

a = 1, 2, 3
a # => [1, 2, 3]

Практически същото като a = [1, 2, 3]

Паралелно присвояване

разпакетиране на дясната страна

a, b = [1, 2, 3]
a # => 1
b # => 2

a, b = 1, 2, 3
a # => 1
b # => 2

Паралелно присвояване

със splat аргументи

head, *tail = [1, 2, 3]
head   # => 1
tail   # => [2, 3]

first, *middle, last = 1, 2, 3, 4
first  # => 1
middle # => [2, 3]
last   # => 4

Паралелно присвояване

splat аргументи отдясно

first, *middle, last = 1, [2, 3, 4]
first  # => 1
middle # => []
last   # => [2, 3, 4]

first, *middle, last = 1, *[2, 3, 4]
first  # => 1
middle # => [2, 3]
last   # => 4

Вложено присвояване

head, (title, body) = [1, [2, 3]]
head   # => 1
title  # => 2
body   # => 3

Вложено присвояване и splat-ове

head, (title, *sentences) = 1, [2, 3, 4, 5, 6]
head      # => 1
title     # => 2
sentences # => [3, 4, 5, 6]

Ред на оценка

Бележка за реда на оценка при присвояване — първо отдясно, след това отляво:

x = 0
a, b, c = x, (x += 1), (x += 1)
x # => 2
a # => 0
b # => 1
c # => 2

Променливата _

Променливата _

Може да ползвате едно име само един път, когато то се среща в списък с параметри на метод, блок и прочее.

Proc.new { |a, b, a| } # SyntaxError: duplicated argument name
Proc.new { |_, b, _| } # => #<Proc:0x007f818af68de0@(irb):23>

Горното важи не само за блокове, но и за методи.

Присвояване в Ruby

Къде важат тези правила?

[[1, [2, 3]], [4, [5, 6]], [7, [8, 9]]].each do |a, (b, c)|
  puts "#{a}, #{b}, #{c}"
end
# 1, 2, 3
# 4, 5, 6
# 7, 8, 9

Присвояване в Ruby

Имате ли въпроси по тази тема?

Symbol#to_proc

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

name = ->(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 = ->(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

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:0x002b6c26530df0>:0x002b6c26530da0>
Class.new.new       # => #<#<Class:0x002b6c26530710>:0x002b6c26530698>
Module.new          # => #<Module:0x002b6c265300f8>

Класовете като обекти (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:0x002b8b21c218e8>
Person = Class.new  # => Person

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

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

Влагане на константи

именовани пространства

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

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

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

module MyLibrary
  class Object
  end
end

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

Quine

програма, принтираща кода си

->_{_%_}["->_{_%%_}[%p]"]

Quine

to the eleven

v=0000;eval$s=%q~d=%!^Lcf<LK8,                  _@7gj*LJ=c5nM)Tp1g0%Xv.,S[<>YoP
4ZojjV)O>qIH1/n[|2yE[>:ieC       "%.#%  :::##"       97N-A&Kj_K_><wS5rtWk@*a+Y5
yH?b[F^e7C/56j|pmRe+:)B     "##%      ::##########"     O98(Zh)'Iof*nm.,$C5Nyt=
PPu01Avw^<IiQ=5$'D-y?    "##:         ###############"    g6`YT+qLw9k^ch|K'),tc
6ygIL8xI#LNz3v}T=4W    "#            #.   .####:#######"    lL27FZ0ij)7TQCI)P7u
}RT5-iJbbG5P-DHB<.   "              ##### # :############"   R,YvZ_rnv6ky-G+4U'
$*are@b4U351Q-ug5   "              #######################"   00x8RR%`Om7VDp4M5
PFixrPvl&<p[]1IJ   "              ############:####  %#####"   EGgDt8Lm#;bc4zS^
y]0`_PstfUxOC(q   "              .#############:##%   .##  ."   /,}.YOIFj(k&q_V
zcaAi?]^lCVYp!;  " %%            .################.     #.   "  ;s="v=%04o;ev"%
(;v=(v-($*+[45,  ":####:          :##############%       :   "  ])[n=0].to_i;)%
360)+"al$s=%q#{  "%######.              #########            "  ;;"%c"%126+$s<<
126}";d.gsub!(/  "##########.           #######%             "  |\s|".*"/,"");;
require"zlib"||  "###########           :######.             "  ;d=d.unpack"C*"
d.map{|c|n=(n||  ":#########:           .######: .           "  )*90+(c-2)%91};
e=["%x"%n].pack   " :#######%           :###### #:          "   &&"H*";e=Zlib::
Inflate.inflate(   "  ######%           .####% ::          "   &&e).unpack("b*"
)[0];22.times{|y|   "  ####%             %###             "   ;w=(Math.sqrt(1-(
(y*2.0-21)/22)**(;   " .###:             .#%             "   ;2))*23).floor;(w*
2-1).times{|x|u=(e+    " %##                           "    )[y*z=360,z]*2;u=u[
90*x/w+v+90,90/w];s[(    " #.                        "    ;y*80)+120-w+x]=(""<<
32<<".:%#")[4*u.count((     " .                   "     ;"0"))/u.size]}};;puts\
s+";_ The Qlobe#{" "*18+ (       "#  :#######"       ;"Copyright(C).Yusuke End\
oh, 2010")}";exit~;_ The Qlobe                  Copyright(C).Yusuke Endoh, 2010

Quine

Може да пуснете quine-а от предния слайд като го запазите във файл quine.rb и ползвате това скриптче:

#!/bin/sh

while true
do
  ruby quine.rb | tee quine_result
  mv quine_result quine.rb
  sleep 0.2
done

Hash#to_proc

слайдове от бонус нивото

Да кажем, че имаме следния код:

CURRENCY_NAMES = {
  BGN: 'Bulgarian lev',
  EUR: 'Euro',
  USD: 'United States dollar',
  CHF: 'Swiss frank',
}.freeze

[:BGN, :CHF, :EUR].map { |currency_code| CURRENCY_NAMES[currency_code] }
# => ["Bulgarian lev", "Swiss frank", "Euro"]

Hash#to_proc

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

Нека предположим, че имаме следната имплементация на Hash#to_proc:

class Hash
  def to_proc
    proc { |key| self[key] }
  end
end

Hash#to_proc

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

Тогава можем да направим следното:

CURRENCY_NAMES = {
  BGN: 'Bulgarian lev',
  EUR: 'Euro',
  USD: 'United States dollar',
  CHF: 'Swiss frank',
}.freeze

[:BGN, :CHF, :EUR].map &CURRENCY_NAMES
# => ["Bulgarian lev", "Swiss frank", "Euro"]

Hash#to_proc

още приложения на горното

Удобни изглеждат и следните хипотетични употреби:

VALID_CURRENCIES = {
  BGN: true,
  EUR: true,
  USD: true,
  CHF: true,
}.freeze

valid_currencies = [:BGN, :CHF, :EUR].select(&VALID_CURRENCIES)

# Or:
if currencies.any? &VALID_CURRENCIES
end

# Or:
words.reject(&FORBIDDEN_WORDS)

Текущ 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)

Constant Autoloading in Rails

Въпроси по константи и scope-ове

Имате ли въпроси?

Въпроси