Иванчо има 42 сникърса. Иванчо изяжда 24. Какво има Иванчо сега?
Диабет... Иванчо сега има диабет.
Какво ще бъде изведено след изпълнението на следния код:
def something(a, *b, c)
p b
end
something('foo', 'bar', 'baz', 'larodi')
['bar', 'baz']
Какво ще бъде изведено след изпълнението на следния код:
class Hash
def string_merge(other_hash, separator = ' ')
merge(other_hash) do |_, old_value, new_value|
old_value.to_s + separator + new_value.to_s
end
end
end
answers = {42 => '42'}
p answers.string_merge(42 => 'is the meaning of life')
{42 => '42 is the meaning of life'}
Каква е конвенцията за употреба на !
в края на името на метод?
!
се поставя в края на метод, ако съществуват две версии на метода, с еднакво име и с разлика в поведението. Обикновено удивителната получава "по-опасният" метод, каквото и да означава това.
!
в края не е задължително метод, който мутира обект, както и има методи, които мутират обекти, но не са с удивителна в края (например, Array#pop
).Как може да разберем дали метод е извикан с блок?
#block_given?
Майката на знанието...
# Синтаксис
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'}
Работят и в ламбди, и в блокове
->(foo:, **opts) { [foo, opts] }.call foo: 'bar', larodi: 'baz'
# => ["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:0x002b3e0c0a9310@-:1 (lambda)>
Proc.new { |n| n**2 } # => #<Proc:0x002b3e0c0a8ed8@-: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.име_на_метод
или просто име_на_метод
:
Изпускайте 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:0x002ac396b38e48>
person.me # => #<Person:0x002ac396b38e48>
person.me.me # => #<Person:0x002ac396b38e48>
Полетата не са публично достъпни. Може да ги достигнете само чрез метод.
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