03. Методи. Блокове. Анонимни функции

03. Методи. Блокове. Анонимни функции

03. Методи. Блокове. Анонимни функции

14 октомври 2015

Днес

Въпрос 1

Избройте всички стойности в Ruby, които се оценяват като лъжа.

  • nil
  • false

Въпрос 2

На какво ще се оцени всеки от следните редове:

42.equal? 42
:answer.equal? :answer
'answer'.equal? 'answer'
'answer' == 'answer'
  • true
  • true
  • false
  • true

Въпрос 3

Кой метод на object ще бъде извикан на всеки от следните редове:

puts object
p object
"A shiny #{object}"
  • to_s
  • inspect
  • to_s

Въпрос 4

Какво ще съдържат променливите a, b и c след изпълнението на следния код:

a = {food: 'chocolate cake'}
b = a
c = b[:food]
c.insert 0, 'white '
b[:food]['cake'] = 'milkshake'
  • a #=> {:food=>"white chocolate milkshake"}
  • b #=> {:food=>"white chocolate milkshake"}
  • c #=> "white chocolate milkshake"

Въпрос 5

Ако имаме следната дефиниция на речник: numbers = {one: :eins, two: :zwei}, какво ще върне всеки един от следните три реда код:

numbers[:eins]                  # => ?
numbers.fetch(:two, :something) # => ?
numbers.fetch(:four)            # => ?
  • nil
  • :zwei
  • KeyError

Предизвикателства

припомняне

Първо предизвикателство

предупреждение-изключение

Втора задача

Втора задача

прочетохте ли си условието?

Всяка функция ще връща нова стойност. Няма да променя подадените ѝ данни.

...

Дефинирайте функцията move(snake, direction), която приема съответниете данни и връща нова змия

...

Функциите не трябва да променят подадените им стойности!

Immutability vs Mutability

теория

  • immutable objects - 42, :answer
  • mutable objects - 'wartburg', [2, 4, 6], {name: 'Spaghetti', origin: 'Italy'}

Immutability vs Mutability

пример

car = 'wartburg'        # => "wartburg"
car.insert(-1, 'er')    # => "wartburger"
car = 'wartburg'.freeze # => "wartburg"
car.insert(-1, 'er')    # => RuntimeError: can't modify frozen String

На почит е да се пише immutable код

...но защо?

На почит е да се пише immutable код

...а недостатъци?

Как да пишем immutable код

в Ruby

Методи

дефиниране

Дефинирането става с ключовата дума def. Резултатът от функцията е последният оценен израз, ако няма return някъде по-рано.

def factorial(n)
  if n == 1
    1
  else
    factorial(n - 1) * n
  end
end

Този код яде стек.

Освен това в Ruby няма tail recursion оптимизация.

Методи

отгоре-отгоре

Методи в съществуващи класове

Ще ви трябва за бъдещи домашни

За да добавите метод в съществуващ клас, например Array, просто "отваряте" класа и дефинирате метода:

class Array
  def fourty_second
    self[41]
  end
end

list     = []
list[41] = 'The Universe'

list.fourty_second # => "The Universe"

Методи

return

Можете да излезете от функция с return:

def includes?(array, element)
  array.each do |item|
    return true if item == element
  end
  false
end

Разбира се, такава функция е излишна. Може да ползвате array.include?(element).

Методи

return

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!"

Методи

стойности по подразбиране (2)

Методи

променлив брой аргументи

Методите в 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 е незадължителен и няма нужда да го подавате, ако нямате предпочитания.

Псевдо-keyword arguments

недостатъци

Този "трик" с хешовете се ползва много, понякога прекалено много. Той има и ред недостатъци:

Истински keyword arguments

Горните недостатъци и нуждата водят до появата на истински 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 arguments

Без стойност по подразбиране, 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

**kwargs

Може да вземете "неизползваните" 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

Истински keyword arguments

предимства

Методи

предикати

Името на метод може да завършва на ?. Това се ползва за методи, които връщат лъжа или истина (предикати):

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). Това е несъвместимо с изтърваването на скобите при извикването на метод.

Анонимни функции

ламбди (2)

Може и така:

double = lambda do |x|
  x * 2
end

Важи стандартната конвенция за { } и do/end.

Анонимни функции

ламбди (3)

От 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?

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 }

Тук виждате и как може да викате функция като използвате списък вместо позиционни аргументи.

Блокове

в сигнатурата (2)

Може и така:

def make_block(&block)
  block
end

doubler = make_block { |n| n * 2 }
doubler.call 2 # => 4

Въпроси