04. Procs. Функции от по-висок ред. Класове (част 1)

04. Procs. Функции от по-висок ред. Класове (част 1)

04. Procs. Функции от по-висок ред. Класове (част 1)

24 октомври 2016

Днес

Малко въпроси

Иванчо има 42 сникърса. Иванчо изяжда 24. Какво има Иванчо сега?

Диабет... Иванчо сега има диабет.

Въпрос 1

Какво ще бъде изведено след изпълнението на следния код:

def something(a, *b, c)
  p b
end

something('foo', 'bar', 'baz', 'larodi')
  • ['bar', 'baz']

Въпрос 2

Какво ще бъде изведено след изпълнението на следния код:

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'}

Въпрос 3

Каква е конвенцията за употреба на ! в края на името на метод?

  • Символът ! се поставя в края на метод, ако съществуват две версии на метода, с еднакво име и с разлика в поведението. Обикновено удивителната получава "по-опасният" метод, каквото и да означава това.
  • Метод с ! в края не е задължително метод, който мутира обект, както и има методи, които мутират обекти, но не са с удивителна в края (например, Array#pop).

Въпрос 4

Как може да разберем дали метод е извикан с блок?

  • #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"

Блокове

& при извикване на метод

Ако имате ламбда, която искате да подадете като блок, може да ползвате &:

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 си мисли, че му подавате блок.

Keyword arguments (допълнение)

Работят и в ламбди, и в блокове

->(foo:, **opts) { [foo, opts] }.call foo: 'bar', larodi: 'baz'
# => ["bar", {:larodi=>"baz"}]

Въпроси до тук?

Сега е моментът да ги зададете :-)

Proc.new

където става странно

В Ruby има два вида анонимни функции. Другият е Proc.

double = Proc.new { |x| x * 2 }

double.call(2)
double[2]
double.(2)

Дотук е същото като при lambda, но има някои разлики при извикване.

lambda е специален вид Proc

където става по-странно

Анонимните функции са обекти тип `Proc`, но с вдигнат специален флаг

lambda { |n| n**2 }   # => #<Proc:0x002b3e0c0a9310@-:1 (lambda)>
Proc.new { |n| n**2 } # => #<Proc:0x002b3e0c0a8ed8@-:2>

Разлики между Proc.new и lambda

f =Proc.new { |x, y| p x, y }lambda { |x, y| p x, y }
f.call(1)1 nilArgumentError
f.call(1, 2)1 21 2
f.call(1, 2, 3)1 2ArgumentError
f.call([1, 2])1 2ArgumentError
f.call(*[1, 2])1 21 2

Разлики между Proc.new и lambda

return

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

Блокове, Proc-ове и ламбди

обобщение

Въпроси до тук?

Сега е моментът да ги зададете :-)

Map, filter, reduce

Функционални закачки

Стандартните функционални неща:

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 подобни синоними се срещат често.

Map

и Pink Floyd

#reduce

ако разбирате това, значи сте ОК

#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 са синоними. Ползвайте първото.

#reduce

примерна имплементация

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 }

#reduce

още по-примерна имплементация

За забавлението.

class Array
  def reduce(initial = nil)
    remaining = dup
    buffer    = initial || remaining.shift

    until remaining.empty?
      buffer = yield buffer, remaining.shift
    end

    buffer
  end
end

Въпроси до тук?

Сега е моментът да ги зададете :-)

Блокове

outro

Ruby и ООП

Класове

прост пример

Дефинират се с 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

Класове

полета

Полетата (още: 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

Класове

полета (2)

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

В методите на класа, 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

Атрибути

setter-и

Разбира се, 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

Атрибути

attr_accessor

Последното е досадно за писане. Затова:

class Person
  attr_accessor :age
end

person = Person.new
person.age = 33

person.age # => 33

Атрибути

какво е `attr_accessor`?

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

Въпроси до тук?

Сега е моментът да ги зададете :-)

Атрибути

Meyer's Uniform Access Principle

Обърнете внимание, че следните два реда правят едно и също:

person.age()
person.age

Няма разлика между достъпване на атрибут и извикване на метод, който го изчислява. Това се нарича Uniform Access Principle и като цяло е хубаво нещо.

Конвенции

напомняне

В Ruby важат следните конвенции.

"Отваряне" на класове

Във всеки момент може да "отворите" клас и да му добавите методи.

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

Тялото на класа (2)

Понякога дори е полезно:

class Object
  if RUBY_VERSION <= '1.8.6'
    def tap
      yield self
      self
    end
  end
end

Въпроси