Enumerable
dup
прави "плитко" копие
immutable методите също (+
, drop
, ...)
snake = [[2, 3], [2, 4], [2, 5]] # => [[2, 3], [2, 4], [2, 5]]
grown_snake = snake + [[2, 6]] # => [[2, 3], [2, 4], [2, 5], [2, 6]]
snake.equal?(snake) # => true
snake.equal?(grown_snake) # => false
snake[0] # => [2, 3]
grown_snake[0] # => [2, 3]
snake[0].equal?(grown_snake[0]) # => true
Какво прави def
, ако не се намираме в class
?
def
винаги дефинира метод в някакъв клас
def
не е в дефиниция на клас, отива като private метод на ObjectКакво е *b
в този контекст? Колко аргумента можем да подаваме на тази функция най-много? А най-малко?
def something(a, *b, c)
end
Какво са drink
, size
и syrup
в този контекст, има ли някаква разлика между тях и ако да, каква?
def order(drink:, size: 'grande', syrup: nil)
end
drink
е задължителен аргумент
size
и syrup
имат стойности по подразбиране
Какви начини знаете за извикване на следната анонимна функция?
`a**b` или `a ** b`?
pow = lambda { |a, b| a**b }
pow.call 2, 3
pow[2, 3]
pow.(2, 3)
a**b
Каква е конвенцията за употреба на !
в края на името на метод?
!
се поставя в края на метод, ако съществуват две версии на метода, с еднакво име и с разлика в поведението. Обикновено удивителната получава "по-опасният" метод, каквото и да означава това.
!
в края не е задължително метод, който мутира обект, както и има методи, които мутират обекти, но не са с удивителна в края (например, Array#pop
).# Синтаксис
say_hi = lambda { puts 'Hi there!' }
double = lambda { |x| x * 2 }
pow = lambda { |a, b| a**b }
# Симпатичен синтаксис (1.9+)
say_hi = -> { puts 'Hi there' }
double = ->(x) { x * 2 }
pow = ->(a, b) { a**b }
# Извикване
pow.call 2, 3
pow[2, 3]
pow.(2, 3)
def calculate
result = yield(2)
"The result for 2 is #{result}"
end
calculate { |x| x**2 } # => "The result for 2 is 4"
yield
yield
се оценява до стойността на блокаАко имате ламбда, която искате да подадете като блок, може да ползвате &
:
is_odd = lambda { |n| n.odd? }
filter([1, 2, 3, 4, 5], &is_odd)
filter([1, 2, 3, 4, 5]) { |n| n.odd? }
Горните са (почти) еквиваленти. Има малка разлика в някои други случаи.
Ако искате да вземете блока като обект, има начин:
def invoke_with(*args, &block)
block.call(*args)
end
invoke_with(1, 2) { |a, b| puts a + b }
Ако първият аргумент на функция е хеш, трябва да изтървете скобите на хеша, ако изтървете скобите на метода.
# Валиден код
order drink: 'latte', size: 'grande'
order({drink: 'latte', size: 'grande'})
# Невалиден код
order {drink: 'latte', size: 'grande'}
Във втория случай, Ruby си мисли, че му подавате блок.
Работят и в ламбди, и в блокове
->(foo:, **opts) { p [opts] }.call foo: 'bar', larodi: 'baz'
Сега е моментът да ги зададете :-)
В Ruby има два вида анонимни функции. Другият е Proc.
double = Proc.new { |x| x * 2 }
double.call(2)
double[2]
double.(2)
Дотук е същото като при lambda
, но има някои разлики при извикване.
Анонимните функции са обекти тип `Proc`, но с вдигнат специален флаг
lambda { |n| n**2 } # => #<Proc:0x002ab59a7f7db0@-:1 (lambda)>
Proc.new { |n| n**2 } # => #<Proc:0x002ab59a7f7888@-:2>
f = | Proc.new { |x, y| p x, y } | lambda { |x, y| p x, y } |
---|---|---|
f.call(1) | 1 nil | ArgumentError |
f.call(1, 2) | 1 2 | 1 2 |
f.call(1, 2, 3) | 1 2 | ArgumentError |
f.call([1, 2]) | 1 2 | ArgumentError |
f.call(*[1, 2]) | 1 2 | 1 2 |
yield
ползва семантиката на Proc.new
lambda
return
в lambda
излиза само от тялото на lambda
-таreturn
в Proc
излиза от тялото на метода, в който е изпълнен Proc
-ътdef return_in_lambda
-> { return 42 }.call
'Hello world'
end
def return_in_proc
Proc.new { return 42 }.call
'Hello world'
end
return_in_lambda # => "Hello world"
return_in_proc # => 42
lambda
) и Proc
-овете са анонимни функции
Proc
, с вдигнат специален флаг
{
/}
, do
/end
) и yield
Proc
) и да се извика като анонимна функция
lambda
/Proc
може да се ползва като блокСега е моментът да ги зададете :-)
Стандартните функционални неща:
numbers = [-9, -4, -1, 0, 1, 4, 9]
positive = numbers.select { |n| n >= 0 }
# => [0, 1, 4, 9]
even = numbers.reject { |n| n.odd? }
# => [-4, 0, 4]
squares = numbers.map { |n| n**2 }
# => [81, 16, 1, 0, 1, 16, 81]
roots = numbers.select { |n| n > 0 }.map { |n| n**0.5 }
# => [1.0, 2.0, 3.0]
#select
и #map
имат синоними
#find_all
и #collect
:
numbers = [-9, -4, -1, 0, 1, 4, 9]
squares = numbers.collect { |n| n**2 }
# => [81, 16, 1, 0, 1, 16, 81]
positive = numbers.find_all { |n| n >= 0 }
# => [0, 1, 4, 9]
В Ruby подобни синоними се срещат често.
#reduce
свежда списък до единична стойност с някаква операция:
numbers = [1, 2, 3, 4, 5]
numbers.reduce(0) { |a, b| a + b }
# => 15
numbers.reduce(1) { |a, b| a * b }
# => 120
numbers.reduce { |a, b| a + b }
# => 15
numbers.reduce { |a, b| "#{a}, #{b}" }
# => "1, 2, 3, 4, 5"
#reduce
и #inject
са синоними. Ползвайте първото.
def reduce(array, initial = nil)
remaining = array.dup
buffer = initial || remaining.shift
until remaining.empty?
buffer = yield buffer, remaining.shift
end
buffer
end
reduce([1, 2, 3, 4]) { |a, b| a + b }
reduce([1, 2, 3, 4], 0) { |a, b| a + b }
За забавлението. Неяснотиите — след малко.
class Array
def reduce(initial = nil)
remaining = dup
buffer = initial || remaining.shift
until remaining.empty?
buffer = yield buffer, remaining.shift
end
buffer
end
end
Сега е моментът да ги зададете :-)
Дефинират се с class
. Методите, дефинирани в тялото на класа,
стават методи на инстанциите му. Инстанцират се с ИмеНаКласа.new
.
class Bacon
def chunky?
'yes, of course!'
end
end
bacon = Bacon.new
bacon.chunky? # => "yes, of course!"
class Vector
def initialize(x, y)
@x = x
@y = y
end
end
initialize
играе ролята на конструктор в Ruby
Полетата (още: 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:0x002b39374de6a8>
person.me # => #<Person:0x002b39374de6a8>
person.me.me # => #<Person:0x002b39374de6a8>
Полетата не са публично достъпни. Може да ги достигнете само чрез метод.
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
attr_accessor
е метод, който генерира два метода — #foo
и #foo=
. Достъпен е в дефинициите на класове. Неформален термин за такива
методи е "class macro".
Има ги в изобилие.
Ако ви трябва само getter или setter, може така:
class Person
attr_reader :name
attr_writer :grade
attr_accessor :age, :height
attr :address # като attr_reader
end
Сега е моментът да ги зададете :-)
Обърнете внимание, че следните два реда правят едно и също:
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
Сега е моментът да ги зададете :-)
Ако извикате #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
отново в следващи лекцииСега е моментът да ги зададете :-)
Модулите в 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 { |n| n.odd? } # => [1, 3, 5]
%w(foo plugh barney).map { |word| word.length } # => [3, 5, 6]
[1, 2, 3, 4, 5].reduce { |a, b| a * b } # => 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, за да генерираме тази таблица.
Хешовете също са 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
и други...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]
Сега е моментът да ги зададете :-)
def each_digit
yield 0; yield 1; yield 2; yield 3; yield 4
yield 5; yield 6; yield 7; yield 8; yield 9
end
Как да вземем yield-натите стойности в масив?
result = []
each_digit do |n|
result << n
end
Така става, ама е... какъв беше техническият термин?
"грозно"
Ето как може да стане:
result = enum_for(:each_digit).to_a
Малко е шантаво, но пък много удобно.
Резултатът от enum_for
имплементира Enumerable
:
evens = enum_for(:each_digit).
select { |digit| digit.event? }
Има една хватка, особено удобна за безкрайни функции:
def each_number
n = 0
loop do
n += 1
yield n
end
end
enum_for(:each_number).
lazy.
select { |x| x.odd? }.
take(10)