Reputation: 1816
I don't understand how the below:
counts = Hash.new(0)
File.foreach("testfile") do |line|
line.scan(/\w+/) do |word|
word = word.downcase
counts[word] += 1
end
end
counts.keys.sort.each {|k| print "#{k}:#{counts[k]} "}
Is so much worse than:
words = Fiber.new do
File.foreach("testfile") do |line|
line.scan(/\w+/) do |word|
Fiber.yield word.downcase
end
end
end
counts = Hash.new(0)
while word = words.resume
counts[word] += 1
end
counts.keys.sort.each {|k| print "#{k}:#{counts[k]} "}
Upvotes: 8
Views: 1594
Reputation: 211660
Fibers are a way of suspending and resuming arbitrary blocks of code. An example like this isn't really a great use-case as it doesn't offer any real advantage over the traditional way of reading in lines and processing them.
In this particular example, if you wanted to make it better, you'd write an enumerator-style interface so you could write:
words = WordsReader.new("testfile")
words.each do |word|
# ...
end
Where Fibers become important is in writing asynchronous code. For example, inside the EventMachine environment you need to be able to issue an asynchronous call, suspend a block of code, and resume it when you receive the response.
This ends up looking like this:
async_call(argument1, argument2) do |response_1|
if (response_1.ok?)
async_call(argument3, argument4) do |response_2|
if (response_2.ok?)
async_call(argument5, argument6) do |response_3|
if (response_3.ok?)
do_something(response_1, response_2, response_3)
else
panic_and_fail!
end
end
else
panic_and_fail!
end
end
else
panic_and_fail!
end
end
This sort of nested, nested and re-nested call structure is loosely termed "callback hell" as it gets very difficult to manage once your logic becomes non-trivial. One way to flatten this structure is to employ Fibers. A properly Fiber-ized equivalent is:
begin
response_1 = fiber_call(argument1, argument2)
response_2 = fiber_call(argument3, argument4)
response_3 = fiber_call(argument5, argument6)
do_something(response_1, response_2, response_3)
rescue NotOkay
panic_and_fail!
end
Fibers can take advantage of exceptions, where callback-type code cannot. Exceptions, when used effectively, can massively simplify a block of code, as you can see here. Instead of testing for ok?
on each response, it's expected that the call will throw an exception of type NotOkay
instead.
Callbacks cannot reliably throw exceptions since the initiator of the call has already fallen out of scope when the callback occurs. This is a fundamental limitation of asynchronous programming with callbacks. Fiber driven code maintains a proper call stack, it's merely suspended and resumed as-is, so exceptions properly cascade through the caller.
I've found Fibers to be both simple to understand and very difficult to apply correctly. Most of the time you won't have to use them directly, you'll be using a library that employs them instead. Writing "Fiber-aware" code is not unlike writing "Thread-safe" code. It can be tricky to get right.
Upvotes: 28