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.
@@ -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=
@@ -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
+
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Futuristic
2
+ VERSION = "0.4.3"
3
+ end
@@ -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
@@ -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