05. Класове (част 2). Модули. Enumerable. Наследяване

05. Класове (част 2). Модули. Enumerable. Наследяване

05. Класове (част 2). Модули. Enumerable. Наследяване

26 октомври 2016

Днес

Въпрос 1

Какво ще стане ако достъпим недефинирана инстанционна променлива?

  • Ще получим nil

Въпрос 2

Избройте поне две разлики в поведението на lambda и proc.

  • Ламбдите са стриктни по отношение на броя подадени аргументи, proc обектите не са
  • return в тяло на lambda прекратява работата на lambda-та, а в proc – на обгръщащата функция
  • Проковете правят автоматично разпадане на аргументи, подадени като списък, подобно на блокове, ламбдите – не

Малко преговор

Object#methods

Ако извикате #methods на нещо, ще получите масив от символи с имената на методите му.

Помните ли Array#-?

class Person
  def foo() end
  def bar() end
end

Person.new.methods - Object.new.methods # => [:foo, :bar]

Предефиниране на оператори

Много интуитивно.

class Vector
  attr_accessor :x, :y

  def initialize(x, y)
    @x, @y = x, y
  end

  def +(other)
    Vector.new(x + other.x, y + other.y)
  end

  def inspect
    "Vector.new(#@x, #@y)"
  end
end

Vector.new(1, 5) + Vector.new(3, 10) # => Vector.new(4, 15)

Предефинируеми оператори

Ето и всички оператори, които можете да предефинирате:

Забележки относно предефиниране на оператори

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

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

private

class Person
  def say_hi
    "Hello! I am #{name}"
  end

  private

  def name
    'the Doctor'
  end
end

person = Person.new
person.say_hi     # => "Hello! I am the Doctor"
person.name       # => error: NoMethodError

private (2)

Ако един метод е private, не може да го викате с явен получател. Дори със self.

class Person
  def say_hi
    "Hello! I am #{self.name}"
  end

  private

  def name
    'the Doctor'
  end
end

person = Person.new
person.say_hi     # => error: NoMethodError

protected

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

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

Модули

Модулите в Ruby имат няколко предназначения:

Днес ще разгледаме последното.

Модули

като колекция от методи

Модулите в Ruby просто съдържат методи. Дефинират се подобно на класове:

module UselessStuff
  def almost_pi
    3.1415
  end

  def almost_e
    2.71
  end
end

Модули

миксиране

Модулите могат да се "миксират" с клас. Тогава той получава всички методи на модула като инстанционни методи.

module UselessStuff
  def almost_pi
    3.1415
  end
end

class Something
  include UselessStuff
end

Something.new.almost_pi # => 3.1415

Модули

self

В метод на модула, self е инстанцията от класа, в който модулът е бил миксиран и на която е извикан даденият метод.

module Introducable
  def introduction
    "Hello, I am #{name}"
  end
end

class Person
  include Introducable
  def name() 'The Doctor' end
end

doctor = Person.new
doctor.introduction # => "Hello, I am The Doctor"

Модули

приоритет на методите

Методите на класа имат приоритет пред методите на модула.

module Includeable
  def name() 'Module' end
end

class Something
  def name() 'Class' end
  include Includeable
end

Something.new.name # => "Class"

Модули

приоритет на методите (2)

Ако два модула дефинират един и същи метод, ползва се методът от последно миксирания модул:

module Chunky
  def name() 'chunky' end
end

module Bacon
  def name() 'bacon' end
end

class Something
  include Chunky
  include Bacon
end

Something.new.name # => "bacon"

Модули

приоритет на методите (3)

Просто за информация: методите на mixin-ите имат приоритет пред тези на родителя.

Всичко това е свързано с нещо, наречено ancestor chain, за което ще си говорим следващия път.

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

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

Enumerable

#collect, #find_all и #inject

Помните ли тези методи и техните синоними?

[1, 2, 3, 4, 5].select { |n| n.odd? }           # => [1, 3, 5]
%w(foo plugh barney).map { |word| word.length } # => [3, 5, 6]
[1, 2, 3, 4, 5].reduce { |a, b| a * b }         # => 120

Те са имплементирани в Enumerable, а не в Array.

Всяка колекция в Ruby ги има.

Други методи на Enumerable

all?        any?          chunk       collect          collect_concat
count       cycle         detect      drop             drop_while
each_cons   each_entry    each_slice  each_with_index  each_with_object
entries     find          find_all    find_index       first
flat_map    grep          group_by    include?         inject
lazy        map           max         max_by           member?
min         min_by        minmax      minmax_by        none?
one?        partition     reduce      reject           reverse_each
select      slice_before  sort        sort_by          take
take_while  to_a          zip 

По-нататък ще видите как използваме Enumerable, за да генерираме тази таблица.

Hash ← Enumerable

Хешовете също са Enumerable:

hash = {2 => 3, 4 => 5}

hash.to_a                                 # => [[2, 3], [4, 5]]
hash.map { |p| p[0] + p[1] }              # => [5, 9]
hash.map { |k, v| k + v }                 # => [5, 9]
hash.reduce(0) { |s, p| s + p[0] * p[1] } # => 26

Hash ← Enumerable

бележка под линия

Някои от Enumerable методите в Hash са предефинирани.

hash = {2 => 3, 4 => 5, 6 => 7, 8 => 9}

hash.select { |k, v| v > 6 }      # => {6=>7, 8=>9}
hash.to_a.select { |k, v| v > 6 } # => [[6, 7], [8, 9]]

Enumerable#select връща списък, но Hash#select връща хеш.

Други неща за обхождане

include Enumerable

или как да го ползваме за наши класове

include Enumerable

пример

class FibonacciNumbers
  include Enumerable

  def initialize(limit)
    @limit = limit
  end

  def each
    current, previous = 1, 0

    while current < @limit
      yield current
      current, previous = current + previous, current
    end
  end
end

FibonacciNumbers.new(100).to_a # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

include Enumerable

пример

class StepRange
  include Enumerable

  def initialize(first, last, step)
    @first, @last, @step = first, last, step
  end

  def each
    @first.step(@last, @step) { |n| yield n }
  end
end

StepRange.new(1, 10, 2).select { |n| n > 5 } # => [7, 9]

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

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

#all? и #any?

#all?/#any? връщат истина, ако всички/един елемент(и) от колекцията отговарят на някакво условие.

[1, 2, 3, 4].all? { |x| x.even? } # => false
[1, 2, 3, 4].any? { |x| x.even? } # => true

[2, 4, 6, 8].all? { |x| x.even? } # => true
[2, 4, 6, 8].any? { |x| x.odd? }  # => false

# И разбира се:
[1, 2, 3, 4].any?(&:even?)        # => true

#all? и #any? без блок

#all? и #any? могат да работят и без блок:

[1, 2, 3, nil].all?     # => false
[1, 2, 3, :nil].all?    # => true
[1, 2, 3, false].any?   # => true

#one? и #none?

Аналогични на #all? и #any?. Също могат да работят без блок.

%w(foo bar larodi).one? { |word| word.length == 6 }  # => true
%w(foo bar larodi).one? { |word| word.length == 3 }  # => false

[1, 5, 3].none? { |number| number.even? }   # => true
[1, 2, 3].none? { |number| number.even? }   # => false

[1, 2, 3].one?     # => false
[1, nil, nil].one? # => true

#none? в действие

class Integer
  def prime?
    return false if self == 1
    2.upto(self ** 0.5).none? { |n| self % n == 0 }
  end
end

7.prime? # => true
8.prime? # => false

#each_with_index

#each_with_index yield-ва всеки елемент с индекса му в масива:

%w[foo bar baz].each_with_index do |word, index|
  puts "#{index}. #{word}"
end

Горното извежда:

0. foo
1. bar
2. baz 

#map with index?

Няма #map_with_index.

Затова пък има нещо по-общо:

%w[foo bar baz].map.with_index do |word, index|
  [index, word]
end

# => [[0, "foo"], [1, "bar"], [2, "baz"]]

#group_by

Името казва всичко, което ви е нужно да знаете.

words  = %w(foo bar plugh larodi)
groups = words.group_by { |word| word.length }

groups # => {3=>["foo", "bar"], 5=>["plugh"], 6=>["larodi"]}

#each_slice

#each_slice(n) yield-ва елементите на части по n:

%w(a b c d e f g h).each_slice(3) do |slice|
  p slice
end

Извежда

["a", "b", "c"]
["d", "e", "f"]
["g", "h"] 

#each_cons

#each_cons(n) yield "подмасиви" с n елемента:

[1, 2, 3, 4, 5].each_cons(3) do |cons|
  p cons
end

Извежда

[1, 2, 3]
[2, 3, 4]
[3, 4, 5] 

#include? и #member?

Вече знаете какво прави:

[1, 2, 3, 4].include? 3   # => true
[1, 2, 3, 4].member? 5    # => false

Двете са синоними.

To infinity and beyond!

лирическо отклонение

(0...1.0 / 0.0).include? -1 # => false
1 / 0     # => error: ZeroDivisionError
1.0 / 0.0 # => Infinity

#zip

[1, 2, 3].zip([4, 5, 6])    # => [[1, 4], [2, 5], [3, 6]]
[1, 2].zip([3, 4], [5, 6])  # => [[1, 3, 5], [2, 4, 6]]

#take, #drop, #take_while и #drop_while

[1, 2, 3, 4, 5].take(2)  # => [1, 2]
[1, 2, 3, 4, 5].drop(2)  # => [3, 4, 5]

[1, 3, 5, 6, 7, 9].take_while(&:odd?)  # => [1, 3, 5]
[1, 3, 5, 6, 7, 9].drop_while(&:odd?) # => [6, 7, 9]

Как генерирахме таблицата с методите?

all?        any?          chunk       collect          collect_concat
count       cycle         detect      drop             drop_while
each_cons   each_entry    each_slice  each_with_index  each_with_object
entries     find          find_all    find_index       first
flat_map    grep          group_by    include?         inject
lazy        map           max         max_by           member?
min         min_by        minmax      minmax_by        none?
one?        partition     reduce      reject           reverse_each
select      slice_before  sort        sort_by          take
take_while  to_a          zip 

Как генерирахме таблицата с методите?

кодът

Enumerable.instance_methods.
  sort.
  map { |name| name.to_s.ljust(16) }.
  each_slice(5) { |row| puts row.join '' }

Disclaimer: Леко е редактиран whitespace-а, за да се събере в слайд.

Въпроси по Enumerable?

След това ще продължим с наследяване.

Наследяване

Наследяването в Ruby става така:

class Person
  def name() 'The Doctor' end
end

class PolitePerson < Person
  def introduction
    "Hi, I am #{name}"
  end
end

PolitePerson.new.introduction # => "Hi, I am The Doctor"

Наследяване

ограничения

private методи

Имате достъп до private методите:

class Person
  private
  def name() 'The Doctor' end
end

class PolitePerson < Person
  def introduction() "Hi, I am #{name}" end
end

PolitePerson.new.introduction # => "Hi, I am The Doctor"

Наследяване

#is_a? и #instance_of?

class Base; end
class SpaceStation < Base; end

base    = Base.new
station = SpaceStation.new

base.is_a? Base            # => true
station.is_a? SpaceStation # => true
station.is_a? Base         # => true

base.instance_of? Base            # => true
station.instance_of? SpaceStation # => true
station.instance_of? Base         # => false

Наследяване

#is_a? и #instance_of? (2)

#is_a? има синоним #kind_of?

class Base; end
class SpaceStation < Base; end

base    = Base.new
station = SpaceStation.new

base.kind_of? Base            # => true
station.kind_of? SpaceStation # => true
station.kind_of? Base         # => true

Наследяване

#is_a? и #instance_of? (3)

super

Може да предефинирате метод и да извикате версията на родителя със super.

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super("Mr. #{other}") + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => "Hello Mr. Smith. How do you do?"

super (2)

Ако извикате super без скоби, родителският метод получава същите аргументи.

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => "Hello Smith. How do you do?"

super (3)

super и super() са различни:

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super() + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => error: ArgumentError

Ancestor chain

Ancestor chain (2)

module Foo; end
module Bar; end
module Qux; end

class Base
  include Foo
end

class Derived < Base
  include Bar
  include Qux
end

Derived.ancestors # => [Derived, Qux, Bar, Base, Foo, Object, Kernel, BasicObject]

Ancestor chain (3)

модули, миксирани в други модули

module Foo; end
module Bar; end

module Qux
  include Foo
  include Bar
end

class Thing
  include Qux
end

Thing.ancestors # => [Thing, Qux, Bar, Foo, Object, Kernel, BasicObject]

Ancestor chain (4)

Има само една версия на метода:

module Talking
  def greeting() "Hello, #{name}" end
end

class Base
  include Talking
  def name()        'Base'   end
  def say_hi_base() greeting end
end

class Derived < Base
  include Talking
  def name()           'Derived' end
  def say_hi_derived() greeting  end
end

derived = Derived.new
derived.say_hi_base    # => "Hello, Derived"
derived.say_hi_derived # => "Hello, Derived"
Derived.ancestors      # => [Derived, Base, Talking, Object, Kernel, BasicObject]

Въпроси