futuristic 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/README.md +96 -0
- data/futuristic.gemspec +20 -0
- data/lib/futuristic.rb +9 -0
- data/lib/futuristic/dispatch/future.rb +56 -0
- data/lib/futuristic/dispatch/promise.rb +73 -0
- data/lib/futuristic/futuristic.rb +19 -0
- data/lib/futuristic/version.rb +3 -0
- data/spec/future_spec.rb +25 -0
- data/spec/promise_spec.rb +25 -0
- data/spec/shared_spec.rb +109 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTJmNzk2OThkYzJiMzQ0ZDZmYjJjMGYyODE4ZTVjNWQ1NWNkYjhjOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
N2IzMTc1NGEzYzI0NzQ0NmY5ZTNkMjlkZjM3YzIzZTM2OTgyNjRmMg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ODQ1NjZhODc4MzZiOTQ3ZTQwOWMyNTQ2ZjQ5YjUxYzE0NjVjZmNlY2UzZDk4
|
10
|
+
ZTgwZGFiOGQ4NjBhMGM3ZDg2ZjIzNmMwZTUxNmU5Mjc5ZjVlMTdjN2YzYzE3
|
11
|
+
MDJjNzY5NWU3NWU2ZGQ1NjRhYjkzOTcyMDIzOWZkNzlmMzA3ZmU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWUxNzYxYjQ5YzExMDAyOTk1NDE1MGFkZDcyMjVlYjdjMzRjNDNlMGMyODU1
|
14
|
+
NTk2NGQ1YjI3OTQ5MTQ5NmYxMGY3OTJmNTZkYTkzZDY0NjI3ZTk0Nzg2ODgz
|
15
|
+
NDMzYmRhYzNiNTcyZmM3MzIwNjY0ZDQ0NWYyMzNhOTUyNjE0ODI=
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#Futures and Promises
|
2
|
+
This is a rubymotion implementation of the Futures and Promise pattern, on top of Grand Central [Dispatch](https://github.com/MacRuby/MacRuby/wiki/Dispatch-Module).
|
3
|
+
|
4
|
+
##What are Future and Promises?
|
5
|
+
|
6
|
+
> In computer science, future, promise, and delay refer to constructs used for
|
7
|
+
> synchronizing in some concurrent programming languages. They describe an object
|
8
|
+
> that acts as a proxy for a result that is initially unknown, usually because the
|
9
|
+
> computation of its value is yet incomplete<[source Wikipedia](http://en.wikipedia.org/wiki/Futures_and_promises)>.
|
10
|
+
|
11
|
+
|
12
|
+
##Futures and Promises
|
13
|
+
are objects holding a value which may become available at some point. This value is usually the result of some other computation. Since this computation may fail with an exception, the Future/Promise may also hold an exception in case the computation throws one.
|
14
|
+
|
15
|
+
#Usage:
|
16
|
+
in your Gem file
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'futuristic', :git => 'git://github.com/seanlilmateus/futuristic.git'
|
20
|
+
|
21
|
+
```
|
22
|
+
###how to use Promises
|
23
|
+
```ruby
|
24
|
+
def fibonacci(n)
|
25
|
+
return n if n < 2
|
26
|
+
fib1 = Dispatch::Promise.new { fibonacci(n-1) }
|
27
|
+
fib2 = Dispatch::Promise.new { fibonacci(n-2) }
|
28
|
+
fib1 + fib2
|
29
|
+
end
|
30
|
+
|
31
|
+
p fibonacci(10) # => 55
|
32
|
+
```
|
33
|
+
|
34
|
+
###how to use Futures
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# computation is started
|
38
|
+
future_data = Dispatch::Future.new do
|
39
|
+
bundle = NSBundle.mainBundle
|
40
|
+
plist_path = bundle.pathForResource("map", ofType: "plist")
|
41
|
+
@map_data = load_plist(File.read(plist_path))
|
42
|
+
end
|
43
|
+
|
44
|
+
# you can do something else
|
45
|
+
@table = create_table_named("Future Maps")
|
46
|
+
|
47
|
+
puts "Hello World"
|
48
|
+
|
49
|
+
@table.data = future_data.value # if the computation is done, results with be immediatelly returned, if not done yet it will wait.
|
50
|
+
```
|
51
|
+
|
52
|
+
###Futures using callback
|
53
|
+
```ruby
|
54
|
+
# computation is started
|
55
|
+
future_data = Dispatch::Future.new do
|
56
|
+
bundle = NSBundle.mainBundle
|
57
|
+
plist_path = bundle.pathForResource("map", ofType: "plist")
|
58
|
+
@map_data = load_plist(File.read(plist_path))
|
59
|
+
end
|
60
|
+
|
61
|
+
# you can do something else
|
62
|
+
@table = create_table_named("Future Maps")
|
63
|
+
|
64
|
+
future_data.when_done do |value|
|
65
|
+
# when the computation is done, table data will be setted on the Future Queue
|
66
|
+
# the call back is not executed on the MainQueue/Dispatch::Queue.main
|
67
|
+
@table.data = future_data.value
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
###Module Futuristic
|
72
|
+
```ruby
|
73
|
+
class Request
|
74
|
+
include Dispatch::Futuristic
|
75
|
+
def long_taking_computation
|
76
|
+
sleep 10
|
77
|
+
42
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
request = Request.new
|
82
|
+
computation = request.future.long_taking_computation
|
83
|
+
|
84
|
+
#you can do something else while the computation is been executed on a background queue
|
85
|
+
puts "Drink some Kölsch"
|
86
|
+
|
87
|
+
# now you need the result, if it's already finished
|
88
|
+
# you will ge the result, otherwise it will wait untill the computation finish
|
89
|
+
p computation.value # => 42
|
90
|
+
```
|
91
|
+
|
92
|
+
#Todo
|
93
|
+
- Parallel Enumerable
|
94
|
+
- Actor models
|
95
|
+
- documentation and examples
|
96
|
+
|
data/futuristic.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'futuristic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'futuristic'
|
8
|
+
gem.version = Futuristic::VERSION
|
9
|
+
gem.date = '2013-03-21'
|
10
|
+
gem.summary = %q{Rubymotion Promise and Futures}
|
11
|
+
gem.description = %q{Rubymotion Promise and Futures helper on top of Grand Central Dispatch}
|
12
|
+
gem.authors = ["Mateus Armando"]
|
13
|
+
gem.email = 'seanlilmateus@yahoo.de'
|
14
|
+
gem.files = ["lib/futuristic.rb"]
|
15
|
+
gem.homepage = 'http://github.com/seanlilmateus/futuristic'
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
end
|
data/lib/futuristic.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
Motion::Project::App.setup do |app|
|
6
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'futuristic/**/*.rb')).each do |file|
|
7
|
+
app.files.unshift(file)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Future
|
2
|
+
# actually future acts just like a Promise,
|
3
|
+
# the only difference is that they are not lazy
|
4
|
+
module Dispatch
|
5
|
+
class Future < Promise
|
6
|
+
# Create s new Future
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# >> future = Dispatch::Future.new { long_taking_task; 10 }
|
10
|
+
# => future.value # 10
|
11
|
+
# Arguments
|
12
|
+
# block, last
|
13
|
+
def self.new(&block)
|
14
|
+
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
|
15
|
+
# new :: a -> Eval (Future a)
|
16
|
+
unless block_given?
|
17
|
+
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Future without a block")
|
18
|
+
end
|
19
|
+
self.alloc.initialization(block)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def when_done(&call_back)
|
24
|
+
@group.notify(@promise_queue) { call_back.call __value__ }
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def initialization(block)
|
30
|
+
super(block)
|
31
|
+
__force__
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Future#description
|
37
|
+
# => <Future: 0x400d382a0 run>
|
38
|
+
#
|
39
|
+
def description
|
40
|
+
state = done? ? :dead : :run
|
41
|
+
NSString.stringWithString(super.gsub(/>/, " #{state}>"))
|
42
|
+
end
|
43
|
+
alias_method :to_s, :description
|
44
|
+
alias_method :inspect, :description
|
45
|
+
|
46
|
+
|
47
|
+
def done?
|
48
|
+
!!@value
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def value
|
53
|
+
__value__
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Dispatch
|
2
|
+
class Promise < BasicObject
|
3
|
+
# new :: Promise a -> Eval a
|
4
|
+
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
|
5
|
+
def self.new(&block)
|
6
|
+
unless block_given?
|
7
|
+
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Promise without a block")
|
8
|
+
end
|
9
|
+
self.alloc.initialization(block)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# setup Grand Central Dispatch concurrent Queue and Group
|
14
|
+
def initialization(block)
|
15
|
+
init
|
16
|
+
@computation = block
|
17
|
+
# Groups are just simple layers on top of semaphores.
|
18
|
+
@group = ::Dispatch::Group.new
|
19
|
+
# Each thread gets its own FIFO queue upon which we will dispatch
|
20
|
+
# the delayed computation passed in the &block variable.
|
21
|
+
@promise_queue = ::Dispatch::Queue.concurrent("org.macruby.#{self.class}-0x#{hash.to_s(16)}") #
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
__value__.inspect
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
private
|
32
|
+
# Asynchronously dispatch the future to the thread-local queue.
|
33
|
+
def __force__
|
34
|
+
@running = true # should only be initiliazed once
|
35
|
+
@promise_queue.async(@group) do
|
36
|
+
begin
|
37
|
+
@value = @computation.call
|
38
|
+
rescue ::Exception => e
|
39
|
+
@exception = e
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Wait fo the computation to finish. If it has already finished, then
|
46
|
+
# just return the value in question.
|
47
|
+
def __value__
|
48
|
+
__force__ unless @running
|
49
|
+
@group.wait
|
50
|
+
::Kernel.raise(@exception) if @exception
|
51
|
+
@value
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# like method_missing for objc
|
56
|
+
# without this 'promise = Dispatch::Promise.new { NSData.dataWithContentsOfFile(file_name) }' will not work
|
57
|
+
# NSString.alloc.initWithData(promise, encoding:NSUTF8StringEncoding)
|
58
|
+
# since promise will not respond to NSData#bytes and return a NSInvalidArgumentException
|
59
|
+
def method_missing(meth, *args, &block)
|
60
|
+
__value__.send(meth, *args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def respond_to_missing?(method_name, include_private = false)
|
65
|
+
__value__.respond_to?(method_name, include_private) || super
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def forwardingTargetForSelector(sel)
|
70
|
+
__value__ if __value__.respond_to?(sel)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Futuristic
|
2
|
+
module Futuristic
|
3
|
+
def future
|
4
|
+
proxy = Class.new(BasicObject) do
|
5
|
+
def initialize(obj)
|
6
|
+
@object = obj
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(meth, *args, &blk)
|
10
|
+
Dispatch::Future.new { @object.send(meth, *args, &blk) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(meth, include_private = false)
|
14
|
+
@object.respond_to?(meth) || super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
proxy.new(self)
|
18
|
+
end
|
19
|
+
end
|
data/spec/future_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
describe Dispatch::Future do
|
2
|
+
|
3
|
+
before { @method = Kernel.method(:future) }
|
4
|
+
|
5
|
+
it "should inherit from BasicObject if available, and not otherwise" do
|
6
|
+
Dispatch::Future.ancestors.should.include BasicObject
|
7
|
+
end
|
8
|
+
|
9
|
+
# behaves_like "A Promise"
|
10
|
+
|
11
|
+
def range_of(range)
|
12
|
+
lambda { |obj| range.include?(obj) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should work in the background" do
|
16
|
+
start = Time.now
|
17
|
+
x = future { sleep 3; 5 }
|
18
|
+
middle = Time.now
|
19
|
+
y = x.value + 5
|
20
|
+
y.should == 10
|
21
|
+
finish = Time.now
|
22
|
+
(middle - start).should.be range_of(0.0..1.0)
|
23
|
+
(finish - start).should.be range_of(3.0..3.9)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
describe Dispatch::Promise do
|
2
|
+
before do
|
3
|
+
@method = Kernel.method(:promise)
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should inherit from BasicObject if available, and not otherwise" do
|
7
|
+
Dispatch::Promise.ancestors.should.include BasicObject
|
8
|
+
end
|
9
|
+
|
10
|
+
behaves_like "A Promise"
|
11
|
+
|
12
|
+
it "should delay execution" do
|
13
|
+
value = 5
|
14
|
+
x = @method.call { value = 10 ; value }
|
15
|
+
value.should == 5
|
16
|
+
y = x + 5
|
17
|
+
y.should == 15
|
18
|
+
value.should == 10
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should delay execution of invalid code" do
|
22
|
+
lambda { 1 / 0 }.should.raise(ZeroDivisionError).message.should.match(/divided by 0/)
|
23
|
+
lambda { x = [ 1, @method.call { x / 0 }]}.should.not.raise(ZeroDivisionError)
|
24
|
+
end
|
25
|
+
end
|
data/spec/shared_spec.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
shared "A Promise" do
|
2
|
+
it "should be createable" do
|
3
|
+
lambda { x = @method.call { 3 + 5 } }.should.not.raise(Exception)
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should accept a block requiring arguments" do
|
7
|
+
lambda { x = @method.call { |x| 3 + 5 }}.should.not.raise(Exception)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be forceable" do
|
11
|
+
x = @method.call { 3 + 5 }
|
12
|
+
x.to_i.should == 8
|
13
|
+
x.should == 8
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should respond_to? a method on the result" do
|
17
|
+
x = @method.call { 3 + 5 }
|
18
|
+
x.respond_to?(:+).should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not respond_to? a method not on the result" do
|
22
|
+
x = @method.call { 3 + 5 }
|
23
|
+
x.respond_to?(:asdf).should == false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should evaluate to a value" do
|
27
|
+
(5 + @method.call { 1 + 2 }).should == 8
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should hold its value" do
|
31
|
+
y = 5
|
32
|
+
x = @method.call { y = y + 5 }
|
33
|
+
x.should == 10
|
34
|
+
x.should == 10
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should only execute once" do
|
38
|
+
y = 1
|
39
|
+
x = @method.call { (y += 1) && false }
|
40
|
+
x.should == false
|
41
|
+
x.should == false
|
42
|
+
y.should == 2
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise exceptions raised during execution when accessed" do
|
46
|
+
y = Object.new
|
47
|
+
y = @method.call { 1 / 0 }
|
48
|
+
lambda { y.inspect }.should.raise(ZeroDivisionError)
|
49
|
+
lambda { y.inspect }.should.raise(ZeroDivisionError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should only execute once when execptions are raised" do
|
53
|
+
y = 1
|
54
|
+
x = @method.call { (y += 1) ; (1 / 0) }
|
55
|
+
lambda { x.inspect }.should.raise(ZeroDivisionError)
|
56
|
+
lambda { x.inspect }.should.raise(ZeroDivisionError)
|
57
|
+
y.should == 2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should remain the same for an object reference" do
|
61
|
+
h = {}
|
62
|
+
x = Object.new
|
63
|
+
h[:test] = @method.call { x }
|
64
|
+
h[:test].should == x
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be eql? for results" do
|
68
|
+
x = Object.new
|
69
|
+
y = @method.call { x }
|
70
|
+
y.should.equal x
|
71
|
+
# this would be ideal, but it can't be done in Ruby. result
|
72
|
+
# objects that have a redefined #eql? should do fine.
|
73
|
+
#x.should eql y
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be equal? for results" do
|
77
|
+
x = Object.new
|
78
|
+
y = @method.call { x }
|
79
|
+
y.should.equal x
|
80
|
+
# this would be ideal, but it can't be done in Ruby.
|
81
|
+
#x.should equal y
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
it "should be thread safe" do
|
87
|
+
x = @method.call { res = 1; 3.times { res = res * 5 ; sleep 1 } ; res }
|
88
|
+
results = []
|
89
|
+
changeds = []
|
90
|
+
changed = false
|
91
|
+
Dispatch::Queue.new('future.rspec').apply(10) do |idx|
|
92
|
+
res = old_res = 125
|
93
|
+
res = x + 5
|
94
|
+
results[idx] = res
|
95
|
+
changed != (res == old_res || idx == 0)
|
96
|
+
changeds[idx] = changed
|
97
|
+
end
|
98
|
+
|
99
|
+
results.each do |result|
|
100
|
+
result.should == 130
|
101
|
+
end
|
102
|
+
|
103
|
+
changeds.each do |changed|
|
104
|
+
changed.should == false
|
105
|
+
end
|
106
|
+
|
107
|
+
changeds.size.should == 10
|
108
|
+
end
|
109
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: futuristic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mateus Armando
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Rubymotion Promise and Futures helper on top of Grand Central Dispatch
|
14
|
+
email: seanlilmateus@yahoo.de
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- futuristic.gemspec
|
21
|
+
- lib/futuristic.rb
|
22
|
+
- lib/futuristic/dispatch/future.rb
|
23
|
+
- lib/futuristic/dispatch/promise.rb
|
24
|
+
- lib/futuristic/futuristic.rb
|
25
|
+
- lib/futuristic/version.rb
|
26
|
+
- spec/future_spec.rb
|
27
|
+
- spec/promise_spec.rb
|
28
|
+
- spec/shared_spec.rb
|
29
|
+
homepage: http://github.com/seanlilmateus/futuristic
|
30
|
+
licenses: []
|
31
|
+
metadata: {}
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 2.0.3
|
49
|
+
signing_key:
|
50
|
+
specification_version: 4
|
51
|
+
summary: Rubymotion Promise and Futures
|
52
|
+
test_files:
|
53
|
+
- spec/future_spec.rb
|
54
|
+
- spec/promise_spec.rb
|
55
|
+
- spec/shared_spec.rb
|