Enumerable
Дефинират се с class
. Методите, дефинирани в тялото на класа,
стават методи на инстанциите му. Инстанцират се с ИмеНаКласа.new
.
class Bacon
def chunky?
'yes, of course!'
end
end
bacon = Bacon.new
bacon.chunky? # "yes, of course!"
Полетата (още: instance variables) имат представка @
.
class Vector
def initialize(x, y)
@x = x
@y = y
end
def length
(@x * @x + @y * @y) ** 0.5
end
end
vector = Vector.new 2.0, 3.0
vector.length() # 3.605551275463989
vector.length # 3.605551275463989
По подразбиране имат стойност nil
.
class Person
def soul
@nothingness
end
end
person = Person.new
person.soul # nil
В метод може да извикате друг със self.име_на_метод
или просто име_на_метод
:
class Person
def initialize(name) @name = name end
def say_hi() puts "My name is #{@name}!" end
def sound_smart() puts "1101000 1101001" end
def talk
self.say_hi
sound_smart
end
end
mel = Person.new 'Mel'
mel.talk
Такова подравняване на методи е гадно, но пък се събира в слайд.
В методите на класа, self
е референция към обекта,
на който е извикан методът. Като this
в Java или C++.
class Person
def me
self
end
end
person = Person.new
person # #<Person:0x41bf9514>
person.me # #<Person:0x41bf9514>
person.me.me # #<Person:0x41bf9514>
Полетата не са публично достъпни. Може да ги достигнете само чрез метод.
class Person
def initialize(age)
@age = age
end
def age
@age
end
def set_age(age)
@age = age
end
end
person = Person.new(33)
person.age # 33
person.set_age 20
person.age # 20
Разбира се, set_age
е гадно име на метод. Може и по-добре:
class Person
def age
@age
end
def age=(value)
@age = value
end
end
person = Person.new
person.age = 33 # Същото като person.age=(33)
person.age # 33
Последното е досадно за писане. Затова:
class Person
attr_accessor :age
end
person = Person.new
person.age = 33
person.age # 33
Ако ви трябва само getter или setter, може така:
class Person
attr_reader :name
attr_writer :grade
attr_accessor :age, :height
end
attr_accessor
е метод, който генерира два метода — #foo
и #foo=
. Достъпен е в дефинициите на класове. Неформален термин за такива
методи е "class macro".
Има ги в изобилие.
Обърнете внимание, че следните два реда правят едно и също:
person.age()
person.age
Няма разлика между достъпване на атрибут и извикване на метод, който го изчислява. Това се нарича Uniform Access Principle и като цяло е хубаво нещо.
В Ruby важат следните конвенции.
UpperCamelCase
SCREAMING_SNAKE_CASE
plain_snake_case
(в т.ч. и инстанционните променливи)Във всеки момент може да "отворите" клас и да му добавите методи. Това вече дори сте го правили.
class Person
def name
'River'
end
end
class Person
def say_hi
"Hi, I am #{name}."
end
end
Person.new.say_hi # "Hi, I am River."
Person.new.name # "River"
Ако дефинирате един метод два пъти, втората дефиниция измества първата.
class Someone
def name
'Tom Baker'
end
def name
'Colin Baker'
end
end
Someone.new.name # => 'Colin Baker'
Тялото на класа е напълно изпълним код:
class Something
a = 1
b = 2
a + b # 3
end
Понякога дори е полезно:
class Object
if RUBY_VERSION <= '1.8.6'
def tap
yield self
self
end
end
end
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
обикновено не се разпознава в тези случаи, понеже не може да ползвате статичен синтактичен анализ за целтаСега е моментът да ги зададете :-)
Ако извикате #methods
на нещо, ще получите масив от символи
с имената на методите му.
Помните ли Array#-
?
class Person
def foo() end
def bar() end
end
Person.new.methods - Object.new.methods # [:foo, :bar]
Много интуитивно.
class Vector
attr_accessor :x, :y
def initialize(x, y)
@x, @y = x, y
end
def +(other)
Vector.new(x + other.x, y + other.y)
end
def inspect
"Vector.new(#@x, #@y)"
end
end
Vector.new(1, 5) + Vector.new(3, 10) # Vector.new(4, 15)
Ето и всички оператори, които можете да предефинирате:
+ - * / % **
^ | &
<= < > >=
<=> == === != =~ !~
[] []= >> <<
, както и унарните + -
a + b
всъщност се извиква a.+(b)
(методът + от класа на a
)
+= >>= &&=
не могат да се предефинират, но действат според очакваното.
a += b
е просто синтаксис за a = a + b
+ -
съответно чрез методите +@ -@
class Person
def say_hi
"Hello! I am #{name}"
end
private
def name
'the Doctor'
end
end
person = Person.new
person.say_hi # "Hello! I am the Doctor"
person.name # error: NoMethodError
Ако един метод е private
, не може да го викате с явен получател.
Дори със self.
class Person
def say_hi
"Hello! I am #{self.name}"
end
private
def name
'the Doctor'
end
end
person = Person.new
person.say_hi # error: NoMethodError
protected
private
protected
и private
отново в следващи лекцииObject#tap
извиква блока със себе си и връща обекта, на който е извикан.
array = [].tap do |items|
items # []
items.equal? array # false
items << 'foo'
'other thing'
end
array # ["foo"]
Имате следния код
(1..10).select { |x| x.odd? }.map { |x| x ** 2 }
Искате да видите какво остава след select
-а:
(1..10).select { |x| x.odd? }.tap { |x| p x }.map { |x| x ** 2 }
class Array
def occurences_count
Hash.new(0).tap do |result|
each { |item| result[item] += 1 }
end
end
end
[nil, 1, 2, 1, :a, 'X', 1, nil].occurences_count # {nil=>2, 1=>3, 2=>1, :a=>1, "X"=>1}
Следните два реда са (почти) еквивалентни:
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]
Всъщност, малко по-сложно е:
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
['Foo', :bar, 3].map(&:to_s).map(&:upcase)
class Symbol
def to_proc
# ...?
end
end
send
send
, за да викате произволни методи на този обект
private
или protected
такива
public_send
, ако искате да не прекрачвате този праг
3.send :+, 4 # 7
class Symbol
def to_proc
->(object, *args) { object.public_send self, *args }
end
end
Модулите в Ruby имат няколко предназначения:
Днес ще разгледаме последното.
Модулите в Ruby просто съдържат методи. Дефинират се подобно на класове:
module UselessStuff
def almost_pi
3.1415
end
def almost_e
2.71
end
end
Модулите могат да се "миксират" с клас. Тогава той получава всички методи на модула като инстанционни методи.
module UselessStuff
def almost_pi
3.1415
end
end
class Something
include UselessStuff
end
Something.new.almost_pi # 3.1415
В метод на модула, self
е инстанцията от класа, в който модулът е бил миксиран и на която е извикан даденият метод.
module Introducable
def introduction
"Hello, I am #{name}"
end
end
class Person
include Introducable
def name() 'The Doctor' end
end
doctor = Person.new
doctor.introduction # "Hello, I am The Doctor"
Методите на класа имат приоритет пред методите на модула.
module Includeable
def name() 'Module' end
end
class Something
def name() 'Class' end
include Includeable
end
Something.new.name # "Class"
Ако два модула дефинират един и същи метод, ползва се методът от последно миксирания модул:
module Chunky
def name() 'chunky' end
end
module Bacon
def name() 'bacon' end
end
class Something
include Chunky
include Bacon
end
Something.new.name # "bacon"
Просто за информация: методите на mixin-ите имат приоритет пред тези на родителя.
Всичко това е свързано с нещо, наречено ancestor chain, за което ще си говорим следващия път.
Помните ли тези методи и техните синоними?
[1, 2, 3, 4, 5].select(&:odd?) # [1, 3, 5]
%w(foo plugh barney).map(&:length) # [3, 5, 6]
[1, 2, 3, 4, 5].reduce(&:*) # 120
Те са имплементирани в Enumerable
, а не в Array
.
Всяка колекция в Ruby ги има.
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
:
hash = {2 => 3, 4 => 5}
hash.to_a # [[2, 3], [4, 5]]
hash.map { |p| p[0] + p[1] } # [5, 9]
hash.map { |k, v| k + v } # [5, 9]
hash.reduce(0) { |s, p| s + p[0] * p[1] } # 26
Някои от Enumerable
методите в Hash
са предефинирани.
hash = {2 => 3, 4 => 5, 6 => 7, 8 => 9}
hash.select { |k, v| v > 6 } # {6=>7, 8=>9}
hash.to_a.select { |k, v| v > 6 } # [[6, 7], [8, 9]]
Enumerable#select
връща списък, но Hash#select
връща хеш.
Hash
, Array
Range
от числа, дати, символи и прочее
Set
и други...#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
Двете са синоними.
[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-а, за да се събере в слайд.
include Enumerable
#each
class FibonacciNumbers
include Enumerable
def initialize(limit)
@limit = limit
end
def each
current, previous = 1, 0
while current < @limit
yield current
current, previous = current + previous, current
end
end
end
FibonacciNumbers.new(100).to_a # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
class StepRange
include Enumerable
def initialize(first, last, step)
@first, @last, @step = first, last, step
end
def each
@first.step(@last, @step) { |n| yield n }
end
end
StepRange.new(1, 10, 2).select { |n| n > 5 } # [7, 9]