Ето за какво да се приготвите по-следващия уикенд.
Какъв ще бъде резултатът от изпълнението на следния код:
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
.
Какъв ще бъде резултатът от изпълнението на следния код:
numbers = {one: 'eins', two: 'zwei'}
numbers.each.with_index.to_a.flatten.size # => ?
6
. След to_a
ще имаме списък с два елемента: [[[:one, "eins"], 0], [[:two, "zwei"], 1]]
.
Какъв ще бъде резултатът от изпълнението на следния код:
numbers = {one: 'eins', two: 'zwei'}.freeze
numbers[:one].upcase!
Няма да има грешка. Хешът numbers
реално не се променя. Мутира се само обектът, към който сочи ключа :one
. За да се предпазим от това, би могло да напишем numbers.each { |_, number| number.freeze }
.
Какъв ще бъде резултатът от изпълнението на следния код:
x = 0
0.upto(100).lazy.map { x += 1 }.map { x += 1 }.take(2)
x # => ?
0
. Причината е, че lazy последователността все още не е оценена.
Може да я оценим с to_a
след take()
. Тогава x
щеше да е 4.
Кодът по-долу няма да приключи никога:
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 }
Кодът по-долу ще работи:
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]
Fiber.new
, Fiber.yield
, Fiber#resume
require 'fiber'
: Fiber.current
, #alive?
и #transfer
Най-простият възможен пример:
fiber = Fiber.new { :larodi }
fiber.resume # :larodi
fiber.resume # error: FiberError
Ако заменим 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
Enumerator
класът в Ruby се възползва от Fiber
.
Това се случва, когато направите (1..100_000).each
.
Exception
и StandardError
.
StandardError < Exception
.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
хваща "само" наследници на 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
Припомняне 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
клаузата се изпълнява винаги.
begin
raise 'tell me - tell me, I implore!' if rand(2).zero?
ensure
puts '????? ??? ?????, "?????????"'
end
rand(2).zero?
връща true
или false
, 50 на 50.else
клаузата се изпълнява когато няма възникнало изключение.
begin
launch_nukes
rescue
puts 'Uh-oh! Something went wrong :('
else
puts 'War... War never changes'
end
launch_nukes
няма удивителна.
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
в метод така:
def execute
begin
potentially_dangerous
rescue SomeException => e
# Handle the error
ensure
# Ensure something always happens
end
end
По-добре е да го запишете без 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
клауза "вдига" същото
изключение, което се обработва.
begin
raise KeyError, 'But high she shoots through air and light'
rescue
puts 'Whoops'
raise
end
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
може да се ползва като модификатор.StandardError
.[].fetch(1) rescue 'baba' # "baba"
raise type, message
всъщност извиква type.exception(message)
за да конструира изключение.
class Thing
def exception(message)
NameError.new(message)
end
end
thing = Thing.new
raise thing, 'whoops' # error: NameError
Може да разделим изключенията на два вида.
rescue
.За първите обикновено създаваме клас. За вторите обикновено ползваме raise
.
KeyError
или IndexError
. Защо?