Last active
January 12, 2018 16:11
-
-
Save seanlilmateus/4134454 to your computer and use it in GitHub Desktop.
Rubymotion GCD Future; with lazy evaluation
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 macruby -wKU | |
framework 'Foundation' | |
module Dispatch | |
module Futuristic | |
def proxy | |
Class.new(BasicObject) do | |
def initialize(obj) | |
@obj = obj | |
end | |
def method_missing(meth, *args, &blk) | |
Future.new { @obj.send(meth, *args, &blk) } | |
end | |
def respond_to_missing?(meth, include_private = false) | |
@obj.respond_to?(meth) | |
end | |
end | |
end | |
def future | |
proxy.new(self) | |
end | |
end | |
class Promise < BasicObject | |
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this | |
def self.new(&block) | |
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Promise without a block") unless block_given? | |
self.alloc.initialization(block) | |
end | |
# setup Grand Central Dispatch concurrent Queue and Group | |
def initialization(block) | |
init | |
@computation = block | |
@arg = nil | |
# Groups are just simple layers on top of semaphores. | |
#Dispatch.once do | |
@group = ::Dispatch::Group.new | |
# Each thread gets its own FIFO queue upon which we will dispatch | |
# the delayed computation passed in the &block variable. | |
@promise_queue = ::Dispatch::Queue.concurrent("org.macruby.dispatch.promise-0x#{hash.to_s(16)}") # | |
#end | |
@value = @success = false | |
self | |
end | |
def inspect | |
__value__.inspect | |
end | |
def done? | |
!!@value | |
end | |
alias_method :ready?, :done? | |
private | |
def __force__ | |
# Asynchronously dispatch the future to the thread-local queue. | |
@promise_queue.async(@group) { @value = @computation.call(*@arg) } | |
end | |
def __value__ | |
# Wait fo the computation to finish. If it has already finished, then | |
# just return the value in question. | |
__force__ unless done? | |
@group.wait | |
@value | |
end | |
# like method_missing for objc | |
# without this 'promise = Dispatch::Promise.new { NSData.dataWithContentsOfFile(file_name) }' will not work | |
# NSString.alloc.initWithData(promise, encoding:NSUTF8StringEncoding) | |
# since promise will not respond to NSData#bytes and return a NSInvalidArgumentException | |
def method_missing(meth, *args, &block) | |
__value__.send(meth, *args, &block) | |
end | |
def respond_to_missing?(method_name, include_private = false) | |
__value__.respond_to?(method_name, include_private) || super | |
end | |
def forwardingTargetForSelector(sel) | |
__value__ if __value__.respond_to?(sel) | |
end | |
end | |
class Future < Promise | |
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this | |
class << self | |
def sequence(*args, &block) | |
array = Array(args).flatten | |
if block_given? | |
result = [] | |
Queue.concurrent(:high).apply(array.count) do |idx| | |
result << Future.new { block.call(array[idx]) } | |
end | |
NSArray.arrayWithArray(result) | |
else | |
array.extend(Futuristic) | |
array.future | |
end | |
end | |
def new(arg=nil, &block) | |
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Future without a block") unless block_given? | |
self.alloc.initialization(arg, block) | |
end | |
end | |
def when_done(&call_back) | |
@group.notify(@promise_queue) { call_back[__value__] } | |
self | |
end | |
def initialization(arg, block) | |
super(block) | |
@arg = arg | |
__force__ | |
self | |
end | |
def description | |
state = done? ? :finished : :running | |
NSString.stringWithString("<#{self.class} 0x#{hash.to_s(16)} STATE:#{state} on #@promise_queue...>") | |
end | |
def value; __value__; end | |
private | |
def method_missing(meth, *args, &block) | |
self.send(meth, *args, &block) | |
end | |
def respond_to_missing?(method_name, include_private = false) | |
self.respond_to?(method_name, include_private) | |
end | |
def forwardingTargetForSelector(sel) | |
self if self.respond_to?(sel) | |
end | |
alias_method :inspect, :description | |
end | |
end | |
class Person | |
include Dispatch::Futuristic | |
attr_accessor :id, :name, :email | |
def initialize(dictionary = {}) | |
setValuesForKeysWithDictionary(dictionary) if dictionary.is_a?(Hash) | |
end | |
def dance(&block) | |
sleep 4 | |
block.call(Dispatch::Queue.current) | |
end | |
def setValue(value, forUndefinedKey:key); end | |
end | |
person = Person.new(name: "Mateus", id:10, email:"mateus@arman.do") | |
# running on background queue | |
person.future.dance do |queue| | |
puts "running queue: #{queue}" | |
end | |
# running on main queue | |
person.dance do |queue| | |
puts "running queue: #{queue}" | |
end | |
urls = ["http://www.facebook.com/", "http://www.google.com/"].map { |url| NSURL.URLWithString(url) } | |
result = Dispatch::Future.sequence(urls) do |url| | |
NSString.stringWithContentsOfURL(url, encoding:NSUTF8StringEncoding, error:nil) | |
end | |
NSLog("Result: %@", result.last.value) | |
p result.first | |
future = Dispatch::Future.new { sleep 0.4; 10} | |
p future.ready? | |
p future.value | |
p future.ready? | |
future = Dispatch::Future.new(40) { |value| value + 2 } | |
p future.ready? | |
p future.value | |
sleep 5 | |
# Dispatch Future | |
the_future = Dispatch::Future.new { sleep 1; 6 / 2 } | |
the_future.when_done { |value| value = 10 } | |
p the_future # <Dispatch::Future 0x400d4bae0 STATE:running on org.macruby.dispatch.promise-0x400d4bae0...> | |
calculation = Dispatch::Future.new { sleep 2; 4 * 4 } | |
p calculation # <Dispatch::Future 0x400d4bae0 STATE:running on org.macruby.dispatch.promise-0x400d4bae0...> | |
calculation.when_done { |value| puts value + 10 } # 26 | |
p calculation.value + 2 # 18 | |
# Dispatch Promise | |
promise = Dispatch::Promise.new { 12 } | |
promise2 = Dispatch::Promise.new { sleep 2; 10 } | |
result = promise2 + promise | |
p result | |
a = Dispatch::Promise.new { 10 / 2} # 10 / 2 = 5 | |
b = Dispatch::Promise.new { a + 1 } # 5 + 1 = 6 | |
c = Dispatch::Promise.new { a - 1 } # 5 - 1 = 4 | |
puts b * c # 24 | |
file_name = "/usr/share/dict/words" | |
promise = Dispatch::Promise.new { NSData.dataWithContentsOfFile(file_name) } | |
string = NSString.alloc.initWithData(promise, encoding:NSUTF8StringEncoding) | |
summe = Dispatch::Future.sequence([*1..100]).inject { |sum, x| sum += x*2-1 } | |
p summe.value | |
futures = [*1..1000].map { |i| Dispatch::Future.new { sleep 1; i * 2 } } | |
p futures.inject(0) { |sum, x| sum += x.value } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
changed from ::Thread.current[:futures] to @futures_queue to make it thread safe
example below used not to work, should be working now: