Гореща тема:
Днес изтече крайният срок за второто домашно в изборен курс, който се води във Факултета по математика и информатика към Софийкия университет.
Студент от курса се оплака, че втората задача е била неочаквано по-трудна от първата. Негови колеги изразиха недоволство от проблемите със системата за ограничения, въведена от преподавателите в курса.
Преди редакционното приключване на новинарската емисия, не успяхме да се свържем с представител на преподавателския екип за коментар.
Кой (и как) може да вика private
методи?
class Something
private
def foo
end
end
self.foo
)Какви са конвенциите за имена на методи, променливи, константи и имена на класове?
UpperCamelCase
- константи (включва имена на класове)normal_snake_case
- променливи, методиSCREAMING_SNAKE_CASE
- константи, които не са имена на класове или модулиНа какво могат да завършват методите в Ruby?
?
, ако са предикати!
, ако имат две версии=
, ако са setter-и%
Как можем да направим наш клас-колекция, която да има всички методи на Enumerable
?
В класа трябва:
include Enumerable
.each
.Array
, Hash
, Range
, Set
и други са все Enumerable
#all?
/#any?
връщат истина, ако всички/един елемент(и) от
колекцията отговарят на някакво условие.
[1, 2, 3, 4].all? { |x| x.even? } # false
[1, 2, 3, 4].any? { |x| x.even? } # true
[2, 4, 6, 8].all? { |x| x.even? } # true
[2, 4, 6, 8].any? { |x| x.odd? } # false
# И разбира се:
[1, 2, 3, 4].any?(&:even?) # true
#all?
и #any?
могат да работят и без блок:
[1, 2, 3, nil].all? # false
[1, 2, 3, :nil].all? # true
[1, 2, 3, false].any? # true
Аналогични на #all?
и #any?
. Също могат да работят без блок.
%w(foo bar larodi).one? { |word| word.length == 6 } # true
%w(foo bar larodi).one? { |word| word.length == 3 } # false
[1, 5, 3].none? { |number| number.even? } # true
[1, 2, 3].none? { |number| number.even? } # false
[1, 2, 3].one? # false
[1, nil, nil].one? # true
#each_with_index
yield-ва всеки елемент с индекса му в масива:
%w[foo bar baz].each_with_index do |word, index|
puts "#{index}. #{word}"
end
Горното извежда:
0. foo 1. bar 2. baz
Името казва всичко, което ви е нужно да знаете.
words = %w(foo bar plugh larodi)
groups = words.group_by { |word| word.length }
groups # {3=>["foo", "bar"], 5=>["plugh"], 6=>["larodi"]}
#each_slice(n)
yield
-ва елементите на части по n
:
%w(a b c d e f g h).each_slice(3) do |slice|
p slice
end
Извежда
["a", "b", "c"] ["d", "e", "f"] ["g", "h"]
#each_cons(n)
yield
"подмасиви" с n
елемента:
[1, 2, 3, 4, 5].each_cons(3) do |cons|
p cons
end
Извежда
[1, 2, 3] [2, 3, 4] [3, 4, 5]
Вече знаете какво прави:
[1, 2, 3, 4].include? 3 # true
[1, 2, 3, 4].member? 5 # false
Двете са синоними.
Бисер от домашните:
(0...1.0 / 0.0).include? -1 # false
1 / 0 # error: ZeroDivisionError
1.0 / 0.0 # Infinity
[1, 2, 3].zip([4, 5, 6]) # [[1, 4], [2, 5], [3, 6]]
[1, 2].zip([3, 4], [5, 6]) # [[1, 3, 5], [2, 4, 6]]
[1, 2, 3, 4, 5].take(2) # [1, 2]
[1, 2, 3, 4, 5].drop(2) # [3, 4, 5]
[1, 3, 5, 6, 7, 9].take_while(&:odd?) # [1, 3, 5]
[1, 3, 5, 6, 7, 9].drop_while(&:odd?) # [6, 7, 9]
all? any? chunk collect collect_concat count cycle detect drop drop_while each_cons each_entry each_slice each_with_index each_with_object entries find find_all find_index first flat_map grep group_by include? inject lazy map max max_by member? min min_by minmax minmax_by none? one? partition reduce reject reverse_each select slice_before sort sort_by take take_while to_a zip
Enumerable.instance_methods.
sort.
map { |name| name.to_s.ljust(16) }.
each_slice(5) { |row| puts row.join '' }
Disclaimer: Леко редактирах whitespace-а, за да се събере в слайд.
След това ще продължим с наследяване.
Наследяването в Ruby става така:
class Person
def name() 'The Doctor' end
end
class PolitePerson < Person
def introduction
"Hi, I am #{name}"
end
end
PolitePerson.new.introduction # "Hi, I am The Doctor"
Object
Object
(с изключение на родителя на Object
...)Имате достъп до private
методите:
class Person
private
def name() 'The Doctor' end
end
class PolitePerson < Person
def introduction() "Hi, I am #{name}" end
end
PolitePerson.new.introduction # "Hi, I am The Doctor"
class Base; end
class SpaceStation < Base; end
base = Base.new
station = SpaceStation.new
base.is_a? Base # true
station.is_a? SpaceStation # true
station.is_a? Base # true
base.instance_of? Base # true
station.instance_of? SpaceStation # true
station.instance_of? Base # false
#is_a?
има синоним #kind_of?
class Base; end
class SpaceStation < Base; end
base = Base.new
station = SpaceStation.new
base.kind_of? Base # true
station.kind_of? SpaceStation # true
station.kind_of? Base # true
#is_a?
по принцип.
#instance_of?
само когато искате да не бъде наследник.
Може да предефинирате метод и да извикате версията на родителя със super
.
class Person
def introduction_to(other)
"Hello #{other}."
end
end
class PolitePerson < Person
def introduction_to(other)
super("Mr. #{other}") + " How do you do?"
end
end
queen = PolitePerson.new
queen.introduction_to('Smith') # "Hello Mr. Smith. How do you do?"
Ако извикате super
без скоби родителският метод получава същите аргументи.
class Person
def introduction_to(other)
"Hello #{other}."
end
end
class PolitePerson < Person
def introduction_to(other)
super + " How do you do?"
end
end
queen = PolitePerson.new
queen.introduction_to('Smith') # "Hello Smith. How do you do?"
super
и super()
са различни:
class Person
def introduction_to(other)
"Hello #{other}."
end
end
class PolitePerson < Person
def introduction_to(other)
super() + " How do you do?"
end
end
queen = PolitePerson.new
queen.introduction_to('Smith') # error: ArgumentError
include
-нати
ancestor chain
module Foo; end
module Bar; end
module Qux; end
class Base
include Foo
end
class Derived < Base
include Bar
include Qux
end
Derived.ancestors # [Derived, Qux, Bar, Base, Foo, Object, Kernel, BasicObject]
module Foo; end
module Bar; end
module Qux
include Foo
include Bar
end
class Thing
include Qux
end
Thing.ancestors # [Thing, Qux, Bar, Foo, Object, Kernel, BasicObject]
Има само една версия на метода:
module Talking
def greeting() "Hello, #{name}" end
end
class Base
include Talking
def name() 'Base' end
def say_hi_base() greeting end
end
class Derived < Base
include Talking
def name() 'Derived' end
def say_hi_derived() greeting end
end
derived = Derived.new
derived.say_hi_base # "Hello, Derived"
derived.say_hi_derived # "Hello, Derived"
alias
Module#alias_method
(клас макро)
String#size
и String#length
alias
: alias :new_method :old_method
alias new_method old_method
(забележете, че това не са символи или низове, a идентификатори)
Module#alias_method
:
alias_method :new_method, :old_method
alias_method 'new_method', 'old_method'
Въпреки името си, alias
прави копие на метод.
class Array
alias old_inject inject
def inject(*args, &block)
puts "I see you are using #inject. Let me help!"
old_inject(*args, &block) * 0.01
end
end
[1, 2, 3, 4, 5, 6].inject { |a, b| a + b } # 0.21
Пример за реална употреба:
def to_s
to_html
end
По-добре да се запише така:
alias_method :to_s, :to_html
Или така:
alias to_s to_html
alias
е ключова дума, докато alias_method
е обикновен метод от Module
(клас макро)
alias
може да подавате като аргументи направо идентификатори на методи, например:
alias original_to_s to_s
ще направи нов синоним на to_s
под името original_to_s
alias_method original_to_s, to_s
ще интерпретира original_to_s
и to_s
като имена на променливи
alias
това не ставаclass Array
[:size, :count, :length].each do |method_name|
alias_method "old_#{method_name}", :size
end
def size
0
end
end
[1, 2, 3].size # 0
[1, 2, 3].old_size # 3
alias
е ключова дума и подлежи на статичен синтактичен анализ, такива инструменти разпознават тези синоними
Module#alias_method
обикновено не се разпознава в тези случаи, понеже не може да ползвате статичен синтактичен анализ за целтаИнтересен gem:
MethodFinder.find('abc', 'ABC')
%w[a b c].find_method { |a| a.unknown(1) ; a == %w[a c] }
"Всичко наследява от Object
"
Object
не наследява от Object
BasicObject
BasicObject
е nil
BasicObject
е минималистичен клас, подходящ за прокситаKernel
е миксиран в Object
Kernel
(#puts
, #eval
и т.н.)
Object
(#inspect
, #tap
, #methods
и т.н.)
Object
Само обекти от същия клас могат да викат protected методи
class Vector
def initialize(x, y) @x, @y = x, y end
def inspect() "Vector.new(#@x, #@y)" end
def +(other)
Vector.new(*coords.zip(other.coords).map { |a, b| a + b })
end
protected
def coords() [@x, @y] end
end
vector = Vector.new(1, 2) + Vector.new(3, 4)
vector # Vector.new(4, 6)
vector.coords # error: NoMethodError
private
се ползва постоянно
protected
почти никога
protected
, защото могат да се викат със self.
отпред
public
. Не го ползваме, а просто слагаме публичните методи отгоре
Module
и Class
Понеже private
и protected
са методи:
class Person
def name() end
def age() end
private :name, :age
end
Или дори:
class String
private :upcase!, :downcase!
end
"Foo".upcase! # error: NoMethodError
Помните ли, че дефиницията на метод връща името на метод като символ?
def foo() end # :foo
Значи може да направите така:
class Person
private def name
"My name is a secret."
end
private def age
"I'm feeling feminine, so my age is a secret, too."
end
end
Класовете и модулите могат да служат като именувани пространства.
module Useless
class Thing
end
end
class Grandfather
class StraightRazor
end
end
Useless::Thing.new # #<Useless::Thing:0x40f67f30>
Grandfather::StraightRazor.new # #<Grandfather::StraightRazor:0x40f67da0>
Ако се намирате в модул, няма нужда да ползвате пълния път до константите:
module Useless
class Thing
end
Thing.new # #<Useless::Thing:0x40f9f278>
end
Useless::Thing.new # #<Useless::Thing:0x40f9f0c0>
Thing.new # error: NameError
def
, module
и class
@foo
)bacon = 2
def foo
chunky = 10
1.times do
chunky # 10
chunky = 44
end
chunky # 44
bacon # error: NameError
end
foo()
module
и class
ви местят из дървото на константите
::
отпред (::Foo
)
Object
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
Не може да ги викате неквалифицирано от инстанцията:
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
Достъпни са в наследниците:
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
Има и други начини за дефиниция на класови методи:
Лошо:
def each(&block)
@numbers.each do |number|
if block_given?
block.call number
else
yield number
end
end
end
Добро:
def each(&block)
@numbers.each &block
end
Лошо:
def empty?
size == 0 ? true : false
end
def non_positive?
return true if self <= 0
false
end
Добро:
def empty?
size == 0
end
def non_positive?
self <= 0
end
Лошо:
number.class.to_s == 'Complex'
Добро 1:
number.class == Complex
Добро 2:
number.is_a? Complex
Лошо:
def size
(map { |item| item = 1 }).reduce { |sum, number| sum + number }
end
Добро:
def size
@numbers.size
end
Лошо:
def << (number)
i = 0
while i < @numbers.length
if (@numbers[i] == number)
return
end
i += 1
end
@numbers << number
end
Добро:
def <<(number)
@numbers << number unless @numbers.include? number
end
Лошо:
TYPES = {
integer: [Integer],
real: [Float, Rational],
complex: [Complex]
}
def initialize(type)
@block = Proc.new do |number|
TYPES[type].reduce(false) { |memo, type| memo || number.is_a?(type) }
end
end
Добро:
def initialize(type)
case type
when :integer then super() { |number| number.is_a?(Integer) }
when :real then super() { |number| number.is_a?(Float) or number.is_a?(Rational) }
when :complex then super() { |number| number.is_a?(Complex) }
end
end
class NumberSet
include Enumerable
def initialize(numbers = [])
@numbers = numbers
end
def each(&block)
@numbers.each(&block)
end
def size
@numbers.size
end
def empty?
@numbers.empty?
end
def <<(number)
@numbers << number unless @numbers.include? number
end
def [](filter)
NumberSet.new @numbers.select { |number| filter.matches? number }
end
end
class Filter
def initialize(&block)
@predicate = block
end
def matches?(number)
@predicate.(number)
end
def |(other)
Filter.new { |number| matches?(number) || other.matches?(number) }
end
def &(other)
Filter.new { |number| matches?(number) && other.matches?(number) }
end
end
class TypeFilter < Filter
def initialize(type)
case type
when :integer then super() { |number| number.kind_of? Integer }
when :real then super() { |number| number.is_a?(Float) ||
number.is_a?(Rational) }
when :complex then super() { |number| number.is_a? Complex }
end
end
end
class SignFilter < Filter
def initialize(sign)
case sign
when :positive then super() { |number| number > 0 }
when :negative then super() { |number| number < 0 }
when :non_positive then super() { |number| number <= 0 }
when :non_negative then super() { |number| number >= 0 }
end
end
end