Created
June 12, 2017 22:07
-
-
Save pbrisbin/fec60a25dd6c07bbeeaf7bd8119d3afd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
class Yielder | |
attr_reader :element | |
def initialize | |
# #yield will block until some other thread calls #next | |
@blocked = true | |
# #next will block until some other thread calls #yield | |
@yielded = false | |
# this is how we know when to raise StopIteration instead of waiting forever | |
# for someone to do something. this requires that the first #yield is always | |
# called before the first #next, which we force in our test here but is | |
# clearly not a guarantee you could ever have in real life. | |
@yield_waiting = false | |
end | |
def yield(x) | |
# confirm the block did in fact try to yield something | |
@yield_waiting = true | |
# wait until something asks us for what we yielded, go back to the other | |
# thread(s) for as little time as possible, ideally only one frame. | |
true while @blocked | |
# immediately re-block | |
@blocked = true | |
# set the element that was yielded | |
@element = x | |
# adjust our state, freeing up #next | |
@yielded = true | |
@yield_waiting = false | |
end | |
def next | |
# we asked for something but nothing has been yielded yet. this is a huge | |
# race that falls apart in practice without a well-placed sleep... | |
raise StopIteration unless @yield_waiting | |
# go ahead, yield me! | |
@blocked = false | |
# same idea, wait as little time as possible... | |
true until @yielded | |
# adjust our state, freeing up #yield | |
@blocked = true | |
@yielded = false | |
# finally, give up dat element | |
@element | |
end | |
end | |
class Fnumerator | |
def initialize(&block) | |
@yielder = Yielder.new | |
Thread.new { block.call(@yielder) } | |
sleep 1 # lulz | |
end | |
def next | |
@yielder.next | |
end | |
def take(n) | |
memo = [] | |
n.times.each do |x| | |
memo << self.next | |
end | |
memo | |
rescue StopIteration | |
memo | |
end | |
end | |
# Yields more than 10 times, should give the taken number of elements without | |
# going into and infinate loop. | |
x = Fnumerator.new do |yielder| | |
loop do | |
yielder.yield(rand(10)) | |
end | |
end | |
p x.take(5) | |
# Yields fewer than 10 times, should give that many elements without waiting | |
# forever for the next yield. | |
y = Fnumerator.new do |yielder| | |
3.times do | |
yielder.yield(rand(10)) | |
end | |
end | |
p y.take(5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment