lazy 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +46 -0
- data/COPYING +2 -0
- data/README +18 -0
- data/Rakefile +3 -0
- data/lib/lazy.rb +223 -0
- data/lib/lazy/futures.rb +10 -0
- data/lib/lazy/threadsafe.rb +10 -0
- metadata +69 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- %YAML:1.0
|
2
|
+
- version: 0.9.6
|
3
|
+
date: 2006-06-16
|
4
|
+
changes:
|
5
|
+
lib/lazy/threadsafe.rb: moved into lib directory, minor tweaks
|
6
|
+
lib/lazy/future.rb: moved into lib directory
|
7
|
+
lib/lazy.rb: moved into lib directory, documentation updates
|
8
|
+
Rakefile: rakefile to do it all from Daniel Harple
|
9
|
+
lazy.gemspec: retired
|
10
|
+
|
11
|
+
- version: 0.9.5
|
12
|
+
date: 2006-02-18
|
13
|
+
changes:
|
14
|
+
lazy/stream.rb: Nuked. No lazy stream API for now, until I can find a more stetic way to do it.
|
15
|
+
lazy.rb:
|
16
|
+
- get rid of Kernel.force
|
17
|
+
- introduce locking API for threadsafety
|
18
|
+
- many documentation tweaks
|
19
|
+
- split Lazy::DivergenceError and Lazy::LazyException
|
20
|
+
- include real promise class in inspect output
|
21
|
+
lazy/threadsafe.rb: adds locking to promises to make them threadsafe
|
22
|
+
lazy/future.rb: transparent futures, built atop promises
|
23
|
+
|
24
|
+
- version: 0.2
|
25
|
+
date: 2005-12-10
|
26
|
+
changes:
|
27
|
+
lazy/stream.rb: added lazy streams with an Enumerable wrapper
|
28
|
+
lazy.rb:
|
29
|
+
- Lazy::Thunk becomes Lazy::Promise
|
30
|
+
- Lazy::Promise#inspect won't force evaluation anymore, but becomes
|
31
|
+
transparent once it is evaluated. This makes life in irb much
|
32
|
+
easier. Thanks to Tom Payne for the idea.
|
33
|
+
- rename force to demand (force is still available as an alias)
|
34
|
+
- rdoc tweaks
|
35
|
+
- dup reason backtrace rather than using it directly in DivergenceError
|
36
|
+
README: added
|
37
|
+
COPYING: added
|
38
|
+
CHANGELOG: added
|
39
|
+
|
40
|
+
- version: 0.1
|
41
|
+
date: 2005-10-05
|
42
|
+
changes:
|
43
|
+
lazy.rb:
|
44
|
+
- trap diverging computations and terminate with DivergenceError
|
45
|
+
- make thunks mostly transparent
|
46
|
+
|
data/COPYING
ADDED
data/README
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
lazy.rb provides lazy evaluation and futures in Ruby.
|
2
|
+
|
3
|
+
For lazy evaluation, the facilities are similar to those provided by R5 Scheme. There are two functions: Kernel.promise (similar to Scheme's delay) which takes a block for later evaluation, and Kernel.demand (similar to Scheme's force), which forces its evaluation (if necessary) and returns its cached result.
|
4
|
+
|
5
|
+
Unlike some Scheme implementations, it is safe to pass ordinary values to demand. Promises are also transparent, meaning that in most cases an evaluated promise is not distinguishable from the actual result object it wraps.
|
6
|
+
|
7
|
+
Originally, promises were not threadsafe unless you required 'lazy/threadsafe',
|
8
|
+
but today they are threadsafe by default. This does entail some amount of
|
9
|
+
synchronization overhead, which in Ruby 1.8 can be reduced by using fastthread.
|
10
|
+
(Other Ruby implementations like JRuby and 1.9 should have lower
|
11
|
+
synchronization overhead to start with.)
|
12
|
+
|
13
|
+
Additionally, the library provides futures, where a computation is run
|
14
|
+
optimistically in a background thread. Futures can be constructed using
|
15
|
+
Kernel.future. Based on promises, they are also transparent. An attempt to
|
16
|
+
demand the result of a future will block until the computation completes.
|
17
|
+
|
18
|
+
lazy.rb is made available under the same license as Ruby.
|
data/Rakefile
ADDED
data/lib/lazy.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# = lazy.rb -- Lazy evaluation in Ruby
|
2
|
+
#
|
3
|
+
# Author:: MenTaLguY
|
4
|
+
#
|
5
|
+
# Copyright 2005-2006 MenTaLguY <mental@rydia.net>
|
6
|
+
#
|
7
|
+
# You may redistribute it and/or modify it under the same terms as Ruby.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'thread'
|
11
|
+
|
12
|
+
module Lazy
|
13
|
+
|
14
|
+
# Raised when a forced computation diverges (e.g. if it tries to directly
|
15
|
+
# use its own result)
|
16
|
+
#
|
17
|
+
class DivergenceError < Exception
|
18
|
+
def initialize( message="Computation diverges" )
|
19
|
+
super( message )
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Wraps an exception raised by a lazy computation.
|
24
|
+
#
|
25
|
+
# The reason we wrap such exceptions in LazyException is that they need to
|
26
|
+
# be distinguishable from similar exceptions which might normally be raised
|
27
|
+
# by whatever strict code we happen to be in at the time.
|
28
|
+
#
|
29
|
+
class LazyException < DivergenceError
|
30
|
+
# the original exception
|
31
|
+
attr_reader :reason
|
32
|
+
|
33
|
+
def initialize( reason )
|
34
|
+
@reason = reason
|
35
|
+
super( "Exception in lazy computation: #{ reason } (#{ reason.class })" )
|
36
|
+
set_backtrace( reason.backtrace.dup ) if reason
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# A promise is just a magic object that springs to life when it is actually
|
41
|
+
# used for the first time, running the provided block and assuming the
|
42
|
+
# identity of the resulting object.
|
43
|
+
#
|
44
|
+
# This impersonation isn't perfect -- a promise wrapping nil or false will
|
45
|
+
# still be considered true by Ruby -- but it's good enough for most purposes.
|
46
|
+
# If you do need to unwrap the result object for some reason (e.g. for truth
|
47
|
+
# testing or for simple efficiency), you may do so via Kernel.demand.
|
48
|
+
#
|
49
|
+
# Formally, a promise is a placeholder for the result of a deferred computation.
|
50
|
+
#
|
51
|
+
class Promise
|
52
|
+
alias __class__ class #:nodoc:
|
53
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
54
|
+
|
55
|
+
def initialize( &computation ) #:nodoc:
|
56
|
+
@mutex = Mutex.new
|
57
|
+
@computation = computation
|
58
|
+
end
|
59
|
+
|
60
|
+
# create this once here, rather than creating a proc object for
|
61
|
+
# every evaluation
|
62
|
+
DIVERGES = lambda { raise DivergenceError.new } #:nodoc:
|
63
|
+
def DIVERGES.inspect #:nodoc:
|
64
|
+
"DIVERGES"
|
65
|
+
end
|
66
|
+
|
67
|
+
def __result__ #:nodoc:
|
68
|
+
@mutex.synchronize do
|
69
|
+
if @computation
|
70
|
+
raise LazyException.new( @exception ) if @exception
|
71
|
+
|
72
|
+
computation = @computation
|
73
|
+
@computation = DIVERGES # trap divergence due to over-eager recursion
|
74
|
+
|
75
|
+
begin
|
76
|
+
@result = Lazy.demand( computation.call( self ) )
|
77
|
+
@computation = nil
|
78
|
+
rescue DivergenceError
|
79
|
+
raise
|
80
|
+
rescue Exception => exception
|
81
|
+
# handle exceptions
|
82
|
+
@exception = exception
|
83
|
+
raise LazyException.new( @exception )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect #:nodoc:
|
92
|
+
@mutex.synchronize do
|
93
|
+
if @computation
|
94
|
+
"#<#{ __class__ } computation=#{ @computation.inspect }>"
|
95
|
+
else
|
96
|
+
@result.inspect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def marshal_dump
|
102
|
+
__result__
|
103
|
+
Marshal.dump( [ @exception, @result ] )
|
104
|
+
end
|
105
|
+
|
106
|
+
def marshal_load( str )
|
107
|
+
@mutex = Mutex.new
|
108
|
+
( @exception, @result ) = Marshal.load( str )
|
109
|
+
@computation = DIVERGES if @exception
|
110
|
+
end
|
111
|
+
|
112
|
+
def respond_to?( message ) #:nodoc:
|
113
|
+
message = message.to_sym
|
114
|
+
message == :__result__ or
|
115
|
+
message == :inspect or
|
116
|
+
message == :marshal_dump or
|
117
|
+
message == :marshal_load or
|
118
|
+
__result__.respond_to? message
|
119
|
+
end
|
120
|
+
|
121
|
+
def method_missing( *args, &block ) #:nodoc:
|
122
|
+
__result__.__send__( *args, &block )
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
Promise.ancestors.each do |c|
|
127
|
+
class << c
|
128
|
+
alias lazy_rb_method_added method_added
|
129
|
+
def method_added( name )
|
130
|
+
lazy_rb_method_added( name )
|
131
|
+
return unless Lazy::Promise < self
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# A promise whose computation runs asynchronously in the background.
|
137
|
+
#
|
138
|
+
class Future < Promise
|
139
|
+
def initialize( scheduler=Thread, &computation ) #:nodoc:
|
140
|
+
task = scheduler.new { computation.call self }
|
141
|
+
super() do
|
142
|
+
raise DivergenceError if scheduler.current == task
|
143
|
+
task.value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module Methods
|
149
|
+
private
|
150
|
+
|
151
|
+
# Used together with Lazy::Methods#demand to implement
|
152
|
+
# lazy evaluation. It returns a promise to evaluate the provided
|
153
|
+
# block at a future time. Evaluation can be forced and the block's
|
154
|
+
# result obtained via
|
155
|
+
#
|
156
|
+
# Implicit evaluation is also supported: the first message sent to it will
|
157
|
+
# force evaluation, after which that message and any subsequent messages
|
158
|
+
# will be forwarded to the result object.
|
159
|
+
#
|
160
|
+
# As an aid to circular programming, the block will be passed a promise
|
161
|
+
# for its own result when it is evaluated. Be careful not to force
|
162
|
+
# that promise during the computation, lest the computation diverge.
|
163
|
+
#
|
164
|
+
def promise( &computation ) #:yields: own_result
|
165
|
+
Lazy::Promise.new &computation
|
166
|
+
end
|
167
|
+
|
168
|
+
# Forces the result of a promise to be computed (if necessary) and returns
|
169
|
+
# the bare result object. Once evaluated, the result of the promise will
|
170
|
+
# be cached. Nested promises will be evaluated together, until the first
|
171
|
+
# non-promise result.
|
172
|
+
#
|
173
|
+
# If called on a value that is not a promise, it will simply return it.
|
174
|
+
#
|
175
|
+
def demand( promise )
|
176
|
+
if promise.respond_to? :__result__
|
177
|
+
promise.__result__
|
178
|
+
else # not really a promise
|
179
|
+
promise
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Schedules a computation to be run asynchronously and returns a promise
|
184
|
+
# for its result. An attempt to force the result of the promise will
|
185
|
+
# block until the computation finishes.
|
186
|
+
#
|
187
|
+
# If +scheduler+ is not specified, the computation is run in a background
|
188
|
+
# thread which is joined when the result is forced. A scheduler should
|
189
|
+
# provide a method, new, which takes a block and returns a task
|
190
|
+
# object. The task object should provide a method, value, which awaits
|
191
|
+
# completion of the task, returning its result or raising the exception that
|
192
|
+
# terminated the task. The Thread class trivially satisfies this
|
193
|
+
# protocol.
|
194
|
+
#
|
195
|
+
# As with Lazy::Methods#demand, this passes the block a promise for its own
|
196
|
+
# result. Use wisely.
|
197
|
+
#
|
198
|
+
def future( scheduler=Thread, &computation ) #:yields: own_result
|
199
|
+
Lazy::Future.new scheduler, &computation
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
extend Methods
|
205
|
+
class << self
|
206
|
+
public :promise, :demand, :future
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
class Module
|
212
|
+
alias lazy_rb_method_added method_added
|
213
|
+
def method_added( name )
|
214
|
+
lazy_rb_method_added( name )
|
215
|
+
if Lazy::Promise < self
|
216
|
+
unless Lazy::Promise.instance_methods( true ).include? name.to_s
|
217
|
+
Lazy::Promise.class_eval { undef_method name }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
data/lib/lazy/futures.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lazy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 6
|
9
|
+
version: 0.9.6
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- MenTaLguY
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-15 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: lazy.rb is a library providing transparent lazy evaluation and futures for Ruby.
|
22
|
+
email: mental@rydia.net
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README
|
29
|
+
files:
|
30
|
+
- README
|
31
|
+
- COPYING
|
32
|
+
- Rakefile
|
33
|
+
- CHANGELOG
|
34
|
+
- lib/lazy.rb
|
35
|
+
- lib/lazy/futures.rb
|
36
|
+
- lib/lazy/threadsafe.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://moonbase.rydia.net/software/lazy.rb/
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --main
|
44
|
+
- README
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.6
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Lazy evaluation for Ruby
|
68
|
+
test_files: []
|
69
|
+
|