09. Fibers. Изключения

09. Fibers. Изключения

09. Fibers. Изключения

5 ноември 2014

Днес

План за 15-16 ноември

Ето за какво да се приготвите по-следващия уикенд.

K2

ще ходим на планина

K2

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

План за 15-16 ноември

традиционната планина

Малка сбирка след лекцията днес

Текущи срокове

Скорост на напредване

Въпрос 1

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

class Sequence
  def generate
    yield
    yield 'hello'
    yield [1, 2]
  end
end

Sequence.new.to_enum.count # => ?

Грешка – недефиниран метод each. Object#to_enum е еквивалентно на Object#enum_for(:each). Правилният код тук би бил Sequence.new.enum_for(:generate).count. Щеше да върне 3.

Въпрос 2

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

numbers = {one: 'eins', two: 'zwei'}
numbers.each.with_index.to_a.flatten.size # => ?

6. След to_a ще имаме списък с два елемента: [[[:one, "eins"], 0], [[:two, "zwei"], 1]].

Въпрос 3

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

numbers = {one: 'eins', two: 'zwei'}.freeze
numbers[:one].upcase!

Няма да има грешка. Хешът numbers реално не се променя. Мутира се само обектът, към който сочи ключа :one. За да се предпазим от това, би могло да напишем numbers.each { |_, number| number.freeze }.

Въпрос 4

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

x = 0
0.upto(100).lazy.map { x += 1 }.map { x += 1 }.take(2)

x # => ?

0. Причината е, че lazy последователността все още не е оценена.

Може да я оценим с to_a след take(). Тогава x щеше да е 4.

Генератор на Fibonacci

Кодът по-долу няма да приключи никога:

class FibonacciNumbers
  def each
    current, previous = 1, 0

    while true
      yield current
      current, previous = current + previous, current
    end
  end
end

FibonacciNumbers.new.each { |number| puts number }

Генератор на Fibonacci с енумератори

Кодът по-долу ще работи:

class FibonacciNumbers
  def each
    current, previous = 1, 0

    while true
      yield current
      current, previous = current + previous, current
    end
  end
end

FibonacciNumbers.new.to_enum.take(5) # [1, 1, 2, 3, 5]

Fibers

Fibers

Най-простият възможен пример:

fiber = Fiber.new { :larodi }

fiber.resume # :larodi
fiber.resume # error: FiberError

Fibonacci с fibers

Ако заменим yield с Fiber.yield, можем да направим нещо като безкраен поток от числа на Фибоначи:

class FibonacciNumbers
  def each
    current, previous = 1, 0

    while true
      Fiber.yield current
      current, previous = current + previous, current
    end
  end
end

fibonacci_stream = Fiber.new { FibonacciNumbers.new.each }

fibonacci_stream.resume # 1
fibonacci_stream.resume # 1
fibonacci_stream.resume # 2
fibonacci_stream.resume # 3

Приложение на fibers

Enumerator-и и fibers

Enumerator класът в Ruby се възползва от Fiber.

Това се случва, когато направите (1..100_000).each.

Изключения

Изключенията в Ruby

Непълна йерархия

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   |   +-- SyntaxError
   |   +-- LoadError
   +-- SecurityError
   +-- StandardError
       +-- ArgumentError
       +-- IndexError
       |   +-- KeyError
       |   +-- StopIteration
       +-- NameError
       |   +-- NoMethodError
       +-- RuntimeError
       +-- TypeError 

После ще видим пълната.

Предизвикване на изключения

# Предизвиква RuntimeError
raise "'Prophet!' said I, 'Thing of evil!" # error: RuntimeError

# Като горното, но с различен текст
raise RuntimeError, 'prophet still, if bird or devil!' # error: RuntimeError

# Друг начин да предизвикаме RuntimeError
raise RuntimeError.new('Whether tempter sent, or whether...') # error: RuntimeError

Хващане на изключения

begin
  puts '...tempest tossed thee here ashore'
  raise NameError, 'Desolate yet all undaunted'
rescue => ex
  ex.message   # "Desolate yet all undaunted"
  ex.class     # NameError
end

Хващане на изключения

хващане на конкретен тип

begin
  raise KeyError, 'on this desert land enchanted'
rescue ArgumentError => ex
  puts 'on this home by horror haunted'
rescue KeyError, TypeError => ex
  ex.message  # "on this desert land enchanted"
  ex.class    # KeyError
end

Какво хваща rescue?

rescue хваща "само" наследници на StandardError, ако не сме указали друго:

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   +-- StandardError
       +-- ArgumentError
       +-- NameError
       |   +-- NoMethodError
       +-- RuntimeError 

Въпрос към вас

Какво ще се случи тук?

begin
  raise KeyError, 'tell me truly, I implore'
rescue IndexError => ex
  puts 'IndexError'
rescue KeyError => ex
  puts 'KeyError'
end

Хващане на изключения

приоритет на rescue клаузите

Припомняне KeyError < IndexError

$eh = 'foo'

begin
  raise KeyError, 'Is there - is there balm in Gilead?'
rescue IndexError => ex
  $eh = 'index'
rescue KeyError => ex
  $eh = 'key'
end

$eh    # "index"

Изпълнява се първия rescue, за който изключението е kind_of? типа.

Запомнете

Динамичните езици обикновено ползват прости правила

Хващане на изключения

ensure клауза

Кодът в ensure клаузата се изпълнява винаги.

begin
  raise 'tell me - tell me, I implore!' if rand(2).zero?
ensure
  puts '????? ??? ?????, "?????????"'
end

Хващане на изключения

else клауза

else клаузата се изпълнява когато няма възникнало изключение.

begin
  launch_nukes
rescue
  puts 'Uh-oh! Something went wrong :('
else
  puts 'War... War never changes'
end

begin/end in all its glory!

begin
  get_a_life
rescue NoFriendsError => ex
  puts 'Goodbye cruel world'
rescue InsufficientVespeneGasError, NotEnoughMineralsError => ex
  puts 'I think I play too much StarCraft'
rescue
  puts ';('
else
  puts 'Woohoo!'
ensure
  puts 'rm -rf ~/.history'
end

rescue в метод

В случай, че ползвате rescue в метод така:

def execute
  begin
    potentially_dangerous
  rescue SomeException => e
    # Handle the error
  ensure
    # Ensure something always happens
  end
end

rescue в метод

Предпочитания вариант

По-добре е да го запишете без begin/end, което е еквивалентно на предното:

def execute
  potentially_dangerous
rescue SomeException => e
  # Handle the error
ensure
  # Ensure something always happens
end

Предизвикване на изключение

по време на обработка на друго

Ако възникне изключение при обработка друго, старото се игнорира и се "вдига" новото.

begin
  raise KeyError
rescue
  raise TypeError
  puts "I'm a line of code, that's never executed ;("
end

raise в rescue

raise в rescue клауза "вдига" същото изключение, което се обработва.

begin
  raise KeyError, 'But high she shoots through air and light'
rescue
  puts 'Whoops'
  raise
end

begin/end

...е израз, като всичко друго в ruby

result = begin
  raise KeyError if rand(3).zero?
  raise NameError if rand(3).zero?
rescue KeyError
  'nyckel'
rescue NameError
  'namn'
else
  'ingenting'
end

result    # "namn"

rescue като модификатор

[].fetch(1) rescue 'baba' # "baba"

Exception#exception

raise type, message всъщност извиква type.exception(message) за да конструира изключение.

class Thing
  def exception(message)
    NameError.new(message)
  end
end

thing = Thing.new
raise thing, 'whoops' # error: NameError

Как да ползваме изключения

Може да разделим изключенията на два вида.

За първите обикновено създаваме клас. За вторите обикновено ползваме raise.

Кога ползваме изключения

Разсъждения

Въпроси