futuristic 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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