На какво ще се оцени всеки от следните редове:
42.equal? 42
:answer.equal? :answer
'answer'.equal? 'answer'
'answer' == 'answer'
true
true
false
true
Кой метод на object
ще бъде извикан на всеки от следните редове:
puts object
p object
"A shiny #{object}"
to_s
inspect
to_s
Какво ще съдържат променливите a
, b
и c
след изпълнението на следния код:
a = {food: 'chocolate cake'}
b = a
c = b[:food]
c.insert 0, 'white '
b[:food]['cake'] = 'milkshake'
Ако имаме следната дефиниция на речник: numbers = {one: :eins, two: :zwei}
, какво ще върне всеки един от следните три реда код:
numbers[:eins] # => ?
numbers.fetch(:two, :something) # => ?
numbers.fetch(:four) # => ?
nil
:zwei
case
if
също е напълно ОК
{key: 1}
ключът е :key
{'key' => 1}
ключът е 'key'
{'key': 1}
ключът е :key
(символ)
{'a-key': 1}
- ключът е символът a-key
42
, :answer
'wartburg'
, [2, 4, 6]
, {name: 'Spaghetti', origin: 'Italy'}
car = 'wartburg' # => "wartburg"
car.insert(-1, 'er') # => "wartburger"
car = 'wartburg'.freeze # => "wartburg"
car.insert(-1, 'er') # => RuntimeError: can't modify frozen String
dup
, clone
(ще говорим за разликите между двата метода в следващи лекции)Дефинирането става с ключовата дума def
. Резултатът от функцията е
последният оценен израз, ако няма return
някъде по-рано.
def factorial(n)
if n == 1
1
else
factorial(n - 1) * n
end
end
Този код яде стек.
Освен това в Ruby няма tail recursion оптимизация.
def
винаги дефинира метод в някакъв клас
def
не е в дефиниция на клас, отива като private
метод на Object
puts
е пример за нещо такова, както и методите, които дефинирате в irb
Object
е удачно само за кратки скриптове
Object
е ужасно лош стилЗа да добавите метод в съществуващ клас, например Array
, просто "отваряте" класа и дефинирате метода:
class Array
def fourty_second
self[41]
end
end
list = []
list[41] = 'The Universe'
list.fourty_second # => "The Universe"
Можете да излезете от функция с return
:
def includes?(array, element)
array.each do |item|
return true if item == element
end
false
end
Разбира се, такава функция е излишна.
Може да ползвате array.include?(element)
.
return
обикновено използваме за специални случаи:
def factorial(n)
return 1 if n == 1
factorial(n - 1) * n
end
Параметрите в Ruby могат да имат стойности по подразбиране:
def order(drink, size = 'large')
"A #{size} #{drink}, please!"
end
order 'tea' # => "A large tea, please!"
order 'coffee', 'small' # => "A small coffee, please!"
Методите в ruby могат да вземат променлив брой аргументи. Параметърът се означава със
*
и при извикване на функцията съдържа списък от аргументите.
def say_hi(name, *drinks)
"Hi, I am #{name} and I enjoy: #{drinks.join(', ')}"
end
say_hi 'Mityo', 'coffee', 'tea', 'water'
# => "Hi, I am Mityo and I enjoy: coffee, tea, water"
Параметърът за променлив брой аргументи може да е на всяка позиция в дефиницията:
def something(*a, b, c)
end
def something(a, *b, c)
end
Очевидно може да има само един такъв параметър във функция.
Когато последният аргумент е хеш, може да изтървете фигурните скоби около него. Долните редове правят едно и също:
def order(drink, preferences)
end
order('Latte', {:size => 'grande', :syrup => 'hazelnut'})
order 'Latte', {:size => 'grande', :syrup => 'hazelnut'}
order 'Latte', :size => 'grande', :syrup => 'hazelnut'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така Ruby симулира извикване на функция с наименовани аргументи. Последният ред работи при версия 1.9+.
Често ще видите код в този вид:
def order(drink, preferences = {})
end
order 'Latte'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така preferences
е незадължителен и няма нужда да го подавате, ако
нямате предпочитания.
Този "трик" с хешовете се ползва много, понякога прекалено много. Той има и ред недостатъци:
preferences[:size]
)
preferences[:size] ||= 'grande'
preferences = {size: 'grande', syrup: 'hazelnut'}.merge(preferences)
Горните недостатъци и нуждата водят до появата на истински keyword arguments в Ruby 2.0.
def order(drink, size: 'grande', syrup: nil)
message = "You ordered a #{size} #{drink}"
message << " with a #{syrup} syrup" if syrup
message
end
order 'Latte'
# => "You ordered a grande Latte"
order 'Latte', syrup: 'hazelnut'
# => "You ordered a grande Latte with a hazelnut syrup"
order 'Latte', filling: 'chocolate'
# => error: ArgumentError
Без стойност по подразбиране, keyword аргументът става задължителен:
def order(drink:, size: 'grande', syrup: nil)
message = "You ordered a #{size} #{drink}"
message << " with a #{syrup} syrup" if syrup
message
end
order drink: 'Latte'
# => "You ordered a grande Latte"
order syrup: 'hazelnut', drink: 'Latte'
# => "You ordered a grande Latte with a hazelnut syrup"
order
# => error: ArgumentError
Може да вземете "неизползваните" keyword аргументи в хеш:
def order(drink:, size: 'grande', **options)
message = "You ordered a #{size} #{drink}"
message << " with these options: #{options.inspect}" unless options.empty?
message
end
order drink: 'Latte'
# => "You ordered a grande Latte"
order syrup: 'hazelnut', drink: 'Latte'
# => "You ordered a grande Latte with these options: {:syrup=>\"hazelnut\"}"
order
# => error: ArgumentError
Името на метод може да завършва на ?
. Това се ползва за методи,
които връщат лъжа или истина (предикати):
def even?(n)
n % 2 == 0
end
even? 2
even? 3
Това е само конвенция.
Името на метод може да завършва на !
.
Това се ползва, когато методът има две версии с различно поведение:
numbers = [4, 1, 3, 2, 5, 0]
numbers.sort # връща нов списък
numbers.sort! # променя списъка на място
В случая, "по-опасният" метод завършва на удивителна.
Ако имате само една версия на метод с такова име, не слагайте удивителна.
Анонимни функции в Ruby се дефинират с lambda
. Имат три начина на извикване:
pow = lambda { |a, b| a**b }
pow.call 2, 3
pow[2, 3]
pow.(2, 3)
За нещастие, не може да извиквате така: double(2)
. Това е несъвместимо с
изтърваването на скобите при извикването на метод.
Може и така:
double = lambda do |x|
x * 2
end
Важи стандартната конвенция за { }
и do
/end
.
От 1.9 има по-симпатичен синтаксис за ламбди:
say_hi = lambda { puts 'Hi there!' }
double = lambda { |x| x * 2 }
divide = lambda { |a, b| a / b }
say_hi = -> { puts 'Hi there' }
double = ->(x) { x * 2 }
divide = -> a, b { a / b }
Всеки метод може да приеме допълнителен аргумент, който е "анонимна функция". Може да
го извикате от метода с yield
:
def twice
yield
yield
end
twice { puts 'Ruby rocks!' }
Блокът може да приема аргументи:
def sequence(first, last, step)
current = first
while current < last
yield current
current += step
end
end
sequence(1, 10, 2) { |n| puts n }
# Извежда 1, 3, 5, 7, 9
yield
се оценява до стойността на блока:
def calculate
result = yield(2)
"The result for 2 is #{result}"
end
calculate { |x| x**2 } # => "The result for 2 is 4"
block_given?
ще ви каже дали методът е извикан с блок:
def i_can_haz_block
if block_given?
puts 'yes'
else
puts 'no'
end
end
i_can_haz_block # no
i_can_haz_block { 'something' } # yes
Ако имате ламбда, която искате да подадете като блок, може да ползвате &
:
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 }
Тук виждате и как може да викате функция като използвате списък вместо позиционни аргументи.
Може и така:
def make_block(&block)
block
end
doubler = make_block { |n| n * 2 }
doubler.call 2 # => 4
Стандартните функционални неща:
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]
#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"