03. Хешове, методи, обекти, класове

03. Хешове, методи, обекти, класове

03. Хешове, методи, обекти, класове

14 октомври 2013

Днес

Бира в сряда

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

Pull Requests

revisited

Array - "списък" или "масив"

спорът със Стефан от миналия път

Обратна връзка за лекциите

по темата с обратната връзка...

Вашата навременна обратна връзка е много важна за нас. Имате поне няколко начина да ни споделите какво мислите:

  1. Пуснете тема във форума на курса
  2. Имейл до fmi@ruby.bg
  3. Псевдо-анонимно – "един приятел ми сподели..."
  4. Говорете с нас по време на междучасието, след лекциите, на по бира...

Малко за задачата

общи впечатления от предадените решения

Конвенции

Именуване

Именуване

не съкращавайте

If you work hard at being clear in your naming, you’ll also rarely need to write comments for the code. Comments are generally only needed when you failed to be clear enough in naming. Treat them as a code smell.

— DHH

Именуване

Clarity over brevity

def make_person_an_outside_subscriber_if_all_accesses_revoked
  person.update_attribute(:outside_subscriber, true) if person.reload.accesses.blank?
end

def shift_records_upward_starting_at(position)
  positioned_records.update_all "position = position - 1",
    ["position >= ?", position]
end

def someone_else_just_finished_writing?(document)
  if event = document.current_version_event
    !event.by_current_creator? and event.updated_at > 1.minute.ago
  end
end

Именуване

лоши примери

Какво прави следният код?

tmp= if (self<0) then (tmp=(-1)*(self)) else (self) end

Ами този?

absolute_value = self > 0 ? self : -self

Именуване

лоши примери

В контекста на метод, проверяващ дали число е просто:

middle = Math.sqrt(self)

vs.

upper_limit = Math.sqrt(self)

Именуване

и преводи

Да обобщим

тайната на добрия код

Правила на Кент Бек за прост и ясен дизайн

My guiding light is Kent Beck's rules of Simple Design (източник):

  1. The code must first be correct (as defined by tests);
  2. then it should be a clear statement of the design (what J.B.Rainsberger calls "no bad names");
  3. then it should contain no duplication (of text, of ideas, or of responsibility);
  4. and finally it must be the smallest code that meets all of the above.

It's time to stop refactoring when the code makes a reasonable stab at meeting those goals, and when any further changes would add no further benefit.

Вече съществуващи методи

и вечното преоткриване на колелото...

Съществуващи методи (2)

Започнете да се запознавате с документацията на следните класове/модули:

Въпрос 1

Какво прави <=> (наричан the space shuttle operator)?

Връща положително число (1), ако първия елемент е по-голям, нула, ако са равни и отрицателно (-1), ако първия е по-малък.

Помнете го като "минус".

Въпрос 2

Какво извежда следният код и защо?

puts 'foo' == 'foo', 'foo'.equal? 'foo'
puts [] == [],       [].equal? []
puts 42 == 42,       42.equal? 42
puts :foo == :foo,   :foo.equal? :foo

== сравнява по стойност, а equal? по идентитет.

Числата и символите се интернират.

Итерация на масиви

преговор от предната сбирка

Итерира се с #each, както всичко останало в Ruby:

primes = [2, 3, 5, 7, 11]

primes.each { |n| puts n }

primes.each do |n|
  puts n
end

"Къдрави скоби" и do/end

преговор от предната сбирка

next и break

next прескача изпълнението до края на блока и продължава със следващия елемент, ала continue в C/Java.

break прекъсва итерацията и продължава изпълнението след блока.

numbers.sort.each do |number|
  next if number.odd?
  break if number > 100

  puts number
end

Хешове

Хешове

общи факти

Хешове

индексиране

numbers = {:one => :eins, :two => :zwei}

numbers[:one]     # :eins
numbers[:three]   # nil

numbers[:three] = :drei

numbers[:three]                     # :drei
numbers.fetch(:four, :keine_ahnung) # :keine_ahnung
numbers.fetch(:four)                # error: KeyError

Хешове

итерация

numbers = {:one => :eins, :two => :zwei}
numbers.keys    # [:one, :two]
numbers.values  # [:eins, :zwei]

numbers.each { |pair| puts pair }
numbers.each { |key, value| puts key, value }

Хешове

разни методи

numbers = {1 => 2, 3 => 4}

numbers.has_key?(:three) # false
numbers.size             # 2
numbers.invert           # {2=>1, 4=>3}
numbers.merge({5 => 6})  # {1=>2, 3=>4, 5=>6}
numbers.to_a             # [[1, 2], [3, 4]]
Hash[1, 2, 3, 4]         # {1=>2, 3=>4}

Хешове

алтернативен синтаксис

Долните два реда произвеждат еднакви хешове. Второто е 1.9+ синтаксис и ще предпочитаме него по конвенция (когато може):

{:one => 1, :two => 2}
{one: 1, two: 2}

Има интересна врътка при извикването на методи, за която ще споменем малко по-натам.

Списъци и хешове

накратко

Методи

дефиниране

Дефинирането става с ключовата дума 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).

Методи

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

Параметрите в 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'

Така 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

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: missing keyword: drink

Истински 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"

Блокове

един пример

Или как можем да напишем filter:

def filter(array)
  result = []
  array.each do |item|
    result << item if yield item
  end
  result
end

filter([1, 2, 3, 4, 5]) { |n| n.odd? } # [1, 3, 5]

Разбира се, такъв метод в Ruby вече съществува – Enumerable#select.

Блокове

#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

Сряда

където пием бира

Въпроси