pure 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.rdoc +7 -0
- data/MANIFEST +44 -20
- data/README.rdoc +553 -16
- data/Rakefile +25 -2
- data/devel/jumpstart.rb +606 -253
- data/install.rb +1 -2
- data/lib/pure.rb +38 -16
- data/lib/pure/bundled_parsers.rb +4 -0
- data/lib/pure/bundled_plugin.rb +49 -0
- data/lib/pure/compiler/ruby_parser.rb +63 -0
- data/lib/pure/delegate.rb +16 -0
- data/lib/pure/driver.rb +33 -0
- data/lib/pure/dsl.rb +2 -0
- data/lib/pure/dsl_definition.rb +11 -0
- data/lib/pure/error.rb +89 -0
- data/lib/pure/extracted_functions.rb +11 -0
- data/lib/pure/extractor.rb +59 -0
- data/lib/pure/names.rb +9 -0
- data/lib/pure/native_worker.rb +27 -0
- data/lib/pure/parser/impl/base_parser.rb +21 -0
- data/lib/pure/parser/impl/internal.rb +31 -0
- data/lib/pure/parser/impl/ripper.rb +96 -0
- data/lib/pure/parser/impl/ruby_parser.rb +77 -0
- data/lib/pure/parser/internal.rb +4 -0
- data/lib/pure/parser/ripper.rb +2 -0
- data/lib/pure/parser/ruby_parser.rb +2 -0
- data/lib/pure/pure.rb +32 -0
- data/lib/pure/pure_module.rb +141 -0
- data/lib/pure/util.rb +15 -0
- data/lib/pure/version.rb +4 -0
- data/spec/compiler_ruby_parser_spec.rb +79 -0
- data/spec/compute_overrides_spec.rb +99 -0
- data/spec/compute_spec.rb +86 -0
- data/spec/compute_thread_spec.rb +29 -0
- data/spec/compute_timed_spec.rb +40 -0
- data/spec/delegate_spec.rb +141 -0
- data/spec/fstat_example.rb +26 -0
- data/spec/parser_sexp_spec.rb +100 -0
- data/spec/parser_spec.rb +18 -31
- data/spec/pure_combine_spec.rb +77 -0
- data/spec/pure_def_spec.rb +186 -0
- data/spec/pure_define_method_spec.rb +24 -0
- data/spec/pure_eval_spec.rb +18 -0
- data/spec/pure_fun_spec.rb +243 -0
- data/spec/pure_nested_spec.rb +35 -0
- data/spec/pure_parser_spec.rb +50 -0
- data/spec/pure_spec.rb +81 -0
- data/spec/pure_spec_base.rb +106 -0
- data/spec/pure_splat_spec.rb +18 -0
- data/spec/pure_two_defs_spec.rb +20 -0
- data/spec/pure_worker_spec.rb +33 -0
- data/spec/readme_spec.rb +36 -32
- data/spec/splat_spec.rb +12 -11
- data/spec/worker_spec.rb +89 -0
- metadata +157 -41
- data/devel/jumpstart/lazy_attribute.rb +0 -38
- data/devel/jumpstart/ruby.rb +0 -44
- data/devel/jumpstart/simple_installer.rb +0 -85
- data/lib/pure/pure_private/creator.rb +0 -27
- data/lib/pure/pure_private/driver.rb +0 -48
- data/lib/pure/pure_private/error.rb +0 -32
- data/lib/pure/pure_private/extractor.rb +0 -79
- data/lib/pure/pure_private/extractor_ripper.rb +0 -95
- data/lib/pure/pure_private/extractor_ruby_parser.rb +0 -47
- data/lib/pure/pure_private/function_database.rb +0 -10
- data/lib/pure/pure_private/singleton_features.rb +0 -67
- data/lib/pure/pure_private/util.rb +0 -23
- data/spec/basic_spec.rb +0 -38
- data/spec/combine_spec.rb +0 -62
- data/spec/common.rb +0 -44
- data/spec/error_spec.rb +0 -146
- data/spec/fun_spec.rb +0 -122
- data/spec/lazy_spec.rb +0 -22
- data/spec/subseqent_spec.rb +0 -42
- data/spec/timed_spec.rb +0 -30
data/CHANGES.rdoc
CHANGED
data/MANIFEST
CHANGED
@@ -3,28 +3,52 @@ MANIFEST
|
|
3
3
|
README.rdoc
|
4
4
|
Rakefile
|
5
5
|
devel/jumpstart.rb
|
6
|
-
devel/jumpstart/lazy_attribute.rb
|
7
|
-
devel/jumpstart/ruby.rb
|
8
|
-
devel/jumpstart/simple_installer.rb
|
9
6
|
install.rb
|
10
7
|
lib/pure.rb
|
11
|
-
lib/pure/
|
12
|
-
lib/pure/
|
13
|
-
lib/pure/
|
14
|
-
lib/pure/
|
15
|
-
lib/pure/
|
16
|
-
lib/pure/
|
17
|
-
lib/pure/
|
18
|
-
lib/pure/
|
19
|
-
lib/pure/
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
8
|
+
lib/pure/bundled_parsers.rb
|
9
|
+
lib/pure/bundled_plugin.rb
|
10
|
+
lib/pure/compiler/ruby_parser.rb
|
11
|
+
lib/pure/delegate.rb
|
12
|
+
lib/pure/driver.rb
|
13
|
+
lib/pure/dsl.rb
|
14
|
+
lib/pure/dsl_definition.rb
|
15
|
+
lib/pure/error.rb
|
16
|
+
lib/pure/extracted_functions.rb
|
17
|
+
lib/pure/extractor.rb
|
18
|
+
lib/pure/names.rb
|
19
|
+
lib/pure/native_worker.rb
|
20
|
+
lib/pure/parser/impl/base_parser.rb
|
21
|
+
lib/pure/parser/impl/internal.rb
|
22
|
+
lib/pure/parser/impl/ripper.rb
|
23
|
+
lib/pure/parser/impl/ruby_parser.rb
|
24
|
+
lib/pure/parser/internal.rb
|
25
|
+
lib/pure/parser/ripper.rb
|
26
|
+
lib/pure/parser/ruby_parser.rb
|
27
|
+
lib/pure/pure.rb
|
28
|
+
lib/pure/pure_module.rb
|
29
|
+
lib/pure/util.rb
|
30
|
+
lib/pure/version.rb
|
31
|
+
spec/compiler_ruby_parser_spec.rb
|
32
|
+
spec/compute_overrides_spec.rb
|
33
|
+
spec/compute_spec.rb
|
34
|
+
spec/compute_thread_spec.rb
|
35
|
+
spec/compute_timed_spec.rb
|
36
|
+
spec/delegate_spec.rb
|
37
|
+
spec/fstat_example.rb
|
38
|
+
spec/parser_sexp_spec.rb
|
26
39
|
spec/parser_spec.rb
|
40
|
+
spec/pure_combine_spec.rb
|
41
|
+
spec/pure_def_spec.rb
|
42
|
+
spec/pure_define_method_spec.rb
|
43
|
+
spec/pure_eval_spec.rb
|
44
|
+
spec/pure_fun_spec.rb
|
45
|
+
spec/pure_nested_spec.rb
|
46
|
+
spec/pure_parser_spec.rb
|
47
|
+
spec/pure_spec.rb
|
48
|
+
spec/pure_spec_base.rb
|
49
|
+
spec/pure_splat_spec.rb
|
50
|
+
spec/pure_two_defs_spec.rb
|
51
|
+
spec/pure_worker_spec.rb
|
27
52
|
spec/readme_spec.rb
|
28
53
|
spec/splat_spec.rb
|
29
|
-
spec/
|
30
|
-
spec/timed_spec.rb
|
54
|
+
spec/worker_spec.rb
|
data/README.rdoc
CHANGED
@@ -8,9 +8,8 @@ Language-level support for automatic parallelism and lazy evaluation.
|
|
8
8
|
== Synopsis
|
9
9
|
|
10
10
|
require 'pure'
|
11
|
-
include Pure
|
12
11
|
|
13
|
-
geometry =
|
12
|
+
geometry = Pure.define do
|
14
13
|
def area(width, height)
|
15
14
|
width*height
|
16
15
|
end
|
@@ -28,32 +27,570 @@ Language-level support for automatic parallelism and lazy evaluation.
|
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
#
|
32
|
-
puts geometry.compute(
|
30
|
+
# Compute the area using 3 parallel threads.
|
31
|
+
puts geometry.compute(3).area
|
33
32
|
# => 63
|
34
33
|
|
35
34
|
# We've done this computation.
|
36
35
|
puts((7 + 2)*(5 + 2))
|
37
36
|
# => 63
|
38
37
|
|
39
|
-
==
|
38
|
+
== Install
|
40
39
|
|
41
|
-
|
40
|
+
% gem install pure
|
42
41
|
|
43
|
-
|
44
|
-
+pure+ block. Above, the +width+ argument to +area+ corresponds by
|
45
|
-
its literal name to the +width+ method, for example.
|
42
|
+
Or for the (non-gem) .tgz package,
|
46
43
|
|
47
|
-
|
44
|
+
% ruby install.rb [--uninstall]
|
45
|
+
|
46
|
+
== Description
|
48
47
|
|
49
|
-
|
50
|
-
|
48
|
+
Pure imports aspects of the pure functional paradigm into Ruby.
|
49
|
+
|
50
|
+
Method and argument names have literal meaning within a
|
51
|
+
<tt>Pure.define</tt> block. In the above example, the +width+
|
52
|
+
argument to +area+ corresponds, by its literal name, to the +width+
|
53
|
+
method.
|
51
54
|
|
52
55
|
Pure does not modify any of the standard classes.
|
53
56
|
|
54
|
-
|
57
|
+
Pure has been tested on MRI 1.8.6, 1.8.7, 1.9.1, 1.9.2, and
|
58
|
+
jruby-1.4.
|
59
|
+
|
60
|
+
== Links
|
61
|
+
|
62
|
+
* Documentation: http://purefunctional.rubyforge.org
|
63
|
+
* Download: http://rubyforge.org/frs/?group_id=8324
|
64
|
+
* Rubyforge home: http://rubyforge.org/projects/purefunctional
|
65
|
+
* Repository: http://github.com/quix/pure
|
66
|
+
|
67
|
+
== Terminology
|
68
|
+
|
69
|
+
<tt>Pure.define</tt> returns a Module instance, called a <em>pure
|
70
|
+
module</em>. The methods of a pure module are called <em>pure
|
71
|
+
functions</em>.
|
72
|
+
|
73
|
+
== DSL
|
74
|
+
|
75
|
+
The pseudo-keyword +pure+, an alias of Pure.define, is included in the
|
76
|
+
global scope with <tt>require 'pure/dsl'</tt>. It is also made
|
77
|
+
available by including Pure::DSL into a class or module.
|
78
|
+
|
79
|
+
== Overrides
|
80
|
+
|
81
|
+
An options hash given to +compute+ is used for overriding functions in
|
82
|
+
the pure module. If the name of a function matches a hash key, the
|
83
|
+
function will be replaced with the corresponding hash value.
|
84
|
+
|
85
|
+
require 'pure/dsl'
|
86
|
+
|
87
|
+
greet = pure do
|
88
|
+
def hello(name)
|
89
|
+
"Hello, #{name}."
|
90
|
+
end
|
91
|
+
|
92
|
+
def name
|
93
|
+
"Bob"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
puts greet.compute(9).hello
|
98
|
+
# => Hello, Bob.
|
99
|
+
|
100
|
+
puts greet.compute(9, :name => "Ralph").hello
|
101
|
+
# => Hello, Ralph.
|
102
|
+
|
103
|
+
puts greet.compute(9, :hello => "Good evening.").hello
|
104
|
+
# => Good evening.
|
105
|
+
|
106
|
+
== Default Number of Functions in Parallel
|
107
|
+
|
108
|
+
The _num_parallel_ argument to compute() may be omitted, in which case
|
109
|
+
the _num_parallel_ determination falls to <tt>Pure.worker</tt>, an
|
110
|
+
object described later in this document.
|
111
|
+
|
112
|
+
require 'pure/dsl'
|
113
|
+
|
114
|
+
Pure.worker.num_parallel = 2
|
115
|
+
|
116
|
+
result = pure do
|
117
|
+
def f(x, y)
|
118
|
+
x + y
|
119
|
+
end
|
120
|
+
|
121
|
+
def x
|
122
|
+
33
|
123
|
+
end
|
124
|
+
end.compute(:y => 44)
|
125
|
+
|
126
|
+
# compute with 2 parallel threads
|
127
|
+
puts result.f # => 77
|
128
|
+
|
129
|
+
== Delegates and Blocks
|
130
|
+
|
131
|
+
When no block is given to +compute+, it returns a delegate for the
|
132
|
+
computation. The computation results are stored in the
|
133
|
+
lazily-evaluated attributes of the delegate. A computation is
|
134
|
+
performed only when an attribute is requested, and an attribute is
|
135
|
+
never recomputed.
|
136
|
+
|
137
|
+
When +compute+ is given a block, the delegate is passed to the block
|
138
|
+
and the return value of +compute+ is the result of the block.
|
139
|
+
|
140
|
+
require 'pure/dsl'
|
141
|
+
|
142
|
+
geometry = pure do
|
143
|
+
def area(width, height)
|
144
|
+
width*height
|
145
|
+
end
|
146
|
+
|
147
|
+
def width(border)
|
148
|
+
7 + border
|
149
|
+
end
|
150
|
+
|
151
|
+
def height(border)
|
152
|
+
5 + border
|
153
|
+
end
|
154
|
+
end
|
55
155
|
|
56
|
-
|
57
|
-
|
156
|
+
area = geometry.compute :border => 2 do |result|
|
157
|
+
puts result.border # => 2
|
158
|
+
puts result[:border] # => 2
|
159
|
+
|
160
|
+
puts result.width # => 9
|
161
|
+
puts result.height # => 7
|
162
|
+
|
163
|
+
result.area
|
164
|
+
end
|
165
|
+
|
166
|
+
puts area # => 63
|
167
|
+
|
168
|
+
Function results may also be accessed with <tt>[]</tt>, as shown in
|
169
|
+
<tt>result[:border]</tt> above.
|
170
|
+
|
171
|
+
== Combining Pure Modules
|
172
|
+
|
173
|
+
Pure modules are regular Module instances. They may be combined
|
174
|
+
freely with +include+.
|
175
|
+
|
176
|
+
require 'pure/dsl'
|
177
|
+
|
178
|
+
greet = pure do
|
179
|
+
def hello(name)
|
180
|
+
"Hello, #{name}."
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
ralph = pure do
|
185
|
+
include greet
|
186
|
+
def name
|
187
|
+
"Ralph"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
puts ralph.compute.hello
|
192
|
+
# => Hello, Ralph.
|
193
|
+
|
194
|
+
== Dynamic Names
|
195
|
+
|
196
|
+
The pseudo-keyword +fun+ is provided for defining a pure function
|
197
|
+
whose name or arguments are unknown at compile time.
|
198
|
+
|
199
|
+
require 'pure/dsl'
|
200
|
+
|
201
|
+
geometry = pure do
|
202
|
+
fun :area => [:width, :height] do |w, h|
|
203
|
+
w*h
|
204
|
+
end
|
205
|
+
|
206
|
+
def width
|
207
|
+
4
|
208
|
+
end
|
209
|
+
|
210
|
+
fun :height do
|
211
|
+
5
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
puts geometry.compute.area # => 20
|
216
|
+
|
217
|
+
Or more realistically,
|
218
|
+
|
219
|
+
require 'pure/dsl'
|
220
|
+
|
221
|
+
stats = pure do
|
222
|
+
files = Dir["*"]
|
223
|
+
|
224
|
+
files.each { |file|
|
225
|
+
fun file do
|
226
|
+
File.stat(fun_name.to_s)
|
227
|
+
end
|
228
|
+
}
|
229
|
+
|
230
|
+
fun :total_size => files do |*values|
|
231
|
+
values.inject(0) { |acc, stat| acc + stat.size }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
stats.compute { |result|
|
236
|
+
puts result["Rakefile"].size # => 505
|
237
|
+
puts result.total_size # => 39355
|
238
|
+
}
|
239
|
+
|
240
|
+
The left of side <tt>=></tt> is the function name. The right side is
|
241
|
+
an array containing the names of the function arguments. The values
|
242
|
+
of the function arguments are passed to the block.
|
243
|
+
|
244
|
+
The next section explains the +fun_name+ call in this example.
|
245
|
+
|
246
|
+
== Referencing Function and Argument Names
|
247
|
+
|
248
|
+
Inside a pure function, +fun_name+ gives the name of the function and
|
249
|
+
+arg_names+ gives the names of its arguments. In the previous example
|
250
|
+
above,
|
251
|
+
|
252
|
+
files.each { |file|
|
253
|
+
fun file do
|
254
|
+
File.stat(fun_name.to_s)
|
255
|
+
end
|
256
|
+
}
|
257
|
+
|
258
|
+
Here, <tt>fun_name.to_s</tt> is exactly the same as +file+. So why
|
259
|
+
not call <tt>File.stat(file)</tt>? Pure functions are extracted from
|
260
|
+
their surrounding context and must therefore use function arguments as
|
261
|
+
the sole means of communication. In this case
|
262
|
+
<tt>File.stat(file)</tt> references +file+ which lies outside the
|
263
|
+
function definition.
|
264
|
+
|
265
|
+
The above is strictly not necessary when the default worker (explained
|
266
|
+
later) is used, however the best strategy is to ignore this detail.
|
267
|
+
|
268
|
+
== Restrictions
|
269
|
+
|
270
|
+
Since the grand scheme of Pure rests upon all functions and function
|
271
|
+
arguments having a name, a pure function defined with +def+ cannot
|
272
|
+
have a <tt>*splat</tt> argument. Naturally this restriction does not
|
273
|
+
apply to pure functions defined with +fun+.
|
274
|
+
|
275
|
+
require 'pure/dsl'
|
276
|
+
|
277
|
+
pure do
|
278
|
+
def f(*args) # => raises Pure::SplatError
|
279
|
+
end
|
280
|
+
|
281
|
+
fun :g => [:x, :y] do |*args| # OK
|
282
|
+
args.map { |a| a**2 }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
A block is never passed to a pure function (except if called manually,
|
287
|
+
of course).
|
288
|
+
|
289
|
+
A pure function cannot have default arguments.
|
290
|
+
|
291
|
+
A pure function should not reference variables declared outside the
|
292
|
+
function definition (see example in previous section).
|
293
|
+
|
294
|
+
== Background
|
295
|
+
|
296
|
+
The user should have a basic understanding of <em>functional
|
297
|
+
programming</em> (see for example
|
298
|
+
http://en.wikipedia.org/wiki/Functional_programming) and the meaning
|
299
|
+
of <em>side effects</em>.
|
300
|
+
|
301
|
+
Every pure function you define must explicitly depend on the data it uses.
|
302
|
+
|
303
|
+
#
|
304
|
+
# BAD example: depending on state DATA.value
|
305
|
+
#
|
306
|
+
geometry = pure do
|
307
|
+
def area(width, height)
|
308
|
+
width*height - DATA.value
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
Unless offset <tt>DATA.value</tt> is really a constant, the
|
313
|
+
computation result is in general not well-defined.
|
314
|
+
|
315
|
+
Just as depending on some changeable state is bad, it is likewise bad
|
316
|
+
to affect a state (to produce a <em>side effect</em>).
|
317
|
+
|
318
|
+
#
|
319
|
+
# BAD example: affecting state
|
320
|
+
#
|
321
|
+
geometry = pure do
|
322
|
+
def area(width, height)
|
323
|
+
ACCUMULATOR.add "more data"
|
324
|
+
width*height
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
Given a pure computation where functions are modifying +ACCUMULATOR+,
|
329
|
+
the end state of +ACCUMULATOR+ is not well-defined, even if
|
330
|
+
the methods of +ACCUMULATOR+ are thread-safe.
|
331
|
+
|
332
|
+
== Philosophy
|
333
|
+
|
334
|
+
Languages which are purely functional (e.g. Haskell) employ special
|
335
|
+
constructs (e.g. monads) for dealing with side-effects. This project
|
336
|
+
is roughly analogous to the converse with respect to Ruby.
|
337
|
+
|
338
|
+
Haskell code is pure (non-side-effecting) by default, with non-pure
|
339
|
+
operations being stuffed into monads. Ruby code is non-pure
|
340
|
+
(side-effecting) by default, with pure code being stuffed into +pure+
|
341
|
+
blocks.
|
342
|
+
|
343
|
+
== Purpose
|
344
|
+
|
345
|
+
Pure has two main goals:
|
346
|
+
|
347
|
+
* Parallelize system-intensive code, e.g. system() calls.
|
348
|
+
|
349
|
+
* Provide a framework for parallelizing Ruby code across an arbitrary number of cores/machines.
|
350
|
+
|
351
|
+
Due to the global VM lock in Ruby 1.9, the actual execution of Ruby VM
|
352
|
+
instructions is not parallelized. However when a Ruby thread is
|
353
|
+
blocking during a system call, other threads will be executed.
|
354
|
+
Contrariwise in Ruby 1.8 the whole interpreter is blocked during a
|
355
|
+
system call.
|
356
|
+
|
357
|
+
The next section addresses the second point above.
|
358
|
+
|
359
|
+
== Technical Details
|
360
|
+
|
361
|
+
=== Parser Plugins
|
362
|
+
|
363
|
+
Pure uses a parser plugin to extract the +def+ and +fun+ definitions
|
364
|
+
inside a pure module. Three parsers are bundled with Pure,
|
365
|
+
|
366
|
+
* Pure::Parser::Internal -- <tt>require 'pure/parser/internal'</tt> -- ruby-1.9.2 only
|
367
|
+
* Pure::Parser::Ripper -- <tt>require 'pure/parser/ripper'</tt> -- ruby-1.9 only
|
368
|
+
* Pure::Parser::RubyParser -- <tt>require 'pure/parser/ruby_parser'</tt> -- any ruby
|
369
|
+
|
370
|
+
The default is tried in that order.
|
371
|
+
|
372
|
+
The current parser may be changed via the <tt>Pure.parser</tt>
|
373
|
+
attribute. A pure module is tied to a parser when the module is
|
374
|
+
created.
|
375
|
+
|
376
|
+
The only requirement for a parser plugin is to properly implement an
|
377
|
+
extract() method.
|
378
|
+
|
379
|
+
=== Worker Plugins
|
380
|
+
|
381
|
+
A worker plugin is a class which defines what happens when a pure
|
382
|
+
function is triggered to execute. A worker instance is tied to the
|
383
|
+
computation delegate returned by compute().
|
384
|
+
|
385
|
+
The default worker looks like this:
|
386
|
+
|
387
|
+
module Pure
|
388
|
+
class NativeWorker
|
389
|
+
attr_reader :num_parallel
|
390
|
+
|
391
|
+
def define_function_begin(pure_module, num_parallel)
|
392
|
+
@num_parallel = num_parallel || self.class.num_parallel
|
393
|
+
@class = Class.new Names do
|
394
|
+
include pure_module
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def define_function(spec)
|
399
|
+
lambda { |*args|
|
400
|
+
@class.new(spec[:name], spec[:args]).send(spec[:name], *args)
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
def define_function_end
|
405
|
+
end
|
406
|
+
|
407
|
+
class << self
|
408
|
+
attr_accessor :num_parallel
|
409
|
+
end
|
410
|
+
@num_parallel = 1
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
The following example illustrates the internals.
|
415
|
+
|
416
|
+
require 'pure/dsl'
|
417
|
+
require 'pure/parser/ruby_parser'
|
418
|
+
|
419
|
+
class FakeWorker
|
420
|
+
#
|
421
|
+
# This method is called for each pure function in the pure module.
|
422
|
+
#
|
423
|
+
# Returns a lambda which computes the function described by spec.
|
424
|
+
#
|
425
|
+
# For this fake worker, we just return the function info.
|
426
|
+
#
|
427
|
+
def define_function(spec)
|
428
|
+
lambda { |*args|
|
429
|
+
[spec, args]
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
#
|
434
|
+
# Called before all define_function calls.
|
435
|
+
#
|
436
|
+
# pure_module is the receiver of compute().
|
437
|
+
#
|
438
|
+
# num_parallel is the hint passed to compute(), or nil if no
|
439
|
+
# hint was given. A worker is free to ignore it, as we do here.
|
440
|
+
#
|
441
|
+
def define_function_begin(pure_module, num_parallel)
|
442
|
+
end
|
443
|
+
|
444
|
+
#
|
445
|
+
# Called after all define_function calls.
|
446
|
+
#
|
447
|
+
def define_function_end
|
448
|
+
end
|
449
|
+
|
450
|
+
#
|
451
|
+
# When a computation begins, the parallelizing engine asks the
|
452
|
+
# worker how many functions to run in parallel.
|
453
|
+
#
|
454
|
+
def num_parallel
|
455
|
+
2
|
456
|
+
end
|
457
|
+
|
458
|
+
class << self
|
459
|
+
#
|
460
|
+
# A num_parallel hint for this worker. A worker is free to
|
461
|
+
# ignore this as well.
|
462
|
+
#
|
463
|
+
attr_accessor :num_parallel
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
adder = pure(Pure::Parser::RubyParser) do
|
468
|
+
def add(left, right)
|
469
|
+
left + right
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
require 'pp'
|
474
|
+
pp adder.compute(FakeWorker, :left => 33, :right => 44).add
|
475
|
+
|
476
|
+
#### output:
|
477
|
+
|
478
|
+
[{:name=>:add,
|
479
|
+
:args=>[:left, :right],
|
480
|
+
:code=>
|
481
|
+
s(:defn,
|
482
|
+
:add,
|
483
|
+
s(:args, :left, :right),
|
484
|
+
s(:scope,
|
485
|
+
s(:block, s(:call, s(:lvar, :left), :+, s(:arglist, s(:lvar, :right)))))),
|
486
|
+
:splat=>false,
|
487
|
+
:default=>false,
|
488
|
+
:file=>"fake_worker.rb",
|
489
|
+
:line=>29,
|
490
|
+
:origin=>:def,
|
491
|
+
:parser=>"Pure::Parser::RubyParser"},
|
492
|
+
[33, 44]]
|
493
|
+
|
494
|
+
Note the Pure::Parser::Internal parser will not generate a
|
495
|
+
<tt>:code</tt> entry, as it just calls Method#parameters and does no
|
496
|
+
parsing.
|
497
|
+
|
498
|
+
=== Compilers
|
499
|
+
|
500
|
+
A compiler converts a function spec (the hash in the previous output)
|
501
|
+
into a callable Ruby object.
|
502
|
+
|
503
|
+
With the addition of a compiler, we have all the components necessary
|
504
|
+
for distributing computations. A function definition and its inputs
|
505
|
+
may be reconstructed on another Ruby interpreter.
|
506
|
+
|
507
|
+
require 'pure/dsl'
|
508
|
+
require 'pure/parser/ruby_parser'
|
509
|
+
require 'pure/compiler/ruby_parser'
|
510
|
+
|
511
|
+
class ExternalWorker
|
512
|
+
def initialize
|
513
|
+
@compiler = Pure::Compiler::RubyParser.new
|
514
|
+
end
|
515
|
+
|
516
|
+
def define_function(spec)
|
517
|
+
lambda { |*args|
|
518
|
+
@compiler.evaluate_function(spec, *args)
|
519
|
+
}
|
520
|
+
end
|
521
|
+
|
522
|
+
def define_function_begin(pure_module, num_parallel)
|
523
|
+
end
|
524
|
+
|
525
|
+
def define_function_end
|
526
|
+
end
|
527
|
+
|
528
|
+
def num_parallel
|
529
|
+
2
|
530
|
+
end
|
531
|
+
|
532
|
+
class << self
|
533
|
+
attr_accessor :num_parallel
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
pure(Pure::Parser::RubyParser) do
|
538
|
+
def add(left, right)
|
539
|
+
left + right
|
540
|
+
end
|
541
|
+
end.compute(ExternalWorker, :left => 33, :right => 44) do |result|
|
542
|
+
puts result.add # => 77
|
543
|
+
end
|
544
|
+
|
545
|
+
See http://github.com/quix/tiamat for an example of a
|
546
|
+
multi-core/multi-machine worker plugin for Pure.
|
547
|
+
|
548
|
+
Pure::Compiler::RubyParser uses RubyParser and Ruby2Ruby together with
|
549
|
+
a code transformer (for instantiating +fun+ definitions) to compile a
|
550
|
+
function spec.
|
551
|
+
|
552
|
+
== Tools
|
553
|
+
|
554
|
+
Lobby your local ruby-core representative to add a built-in sexp
|
555
|
+
extractor and compiler. Lacking these tools, we are stuck this
|
556
|
+
inefficient roundabout process:
|
557
|
+
|
558
|
+
* Ruby interpreter parses the source
|
559
|
+
* RubyParser (redundantly) parses the source and converts it to an sexp
|
560
|
+
* Ruby2Ruby converts the sexp to a string containing Ruby code
|
561
|
+
* call +eval+ on the code string
|
562
|
+
|
563
|
+
However this could be reduced to (for example):
|
564
|
+
|
565
|
+
* Ruby interpreter parses the source
|
566
|
+
* call Proc#to_sexp
|
567
|
+
* call Proc.from_sexp
|
568
|
+
|
569
|
+
== Author
|
570
|
+
|
571
|
+
* James M. Lawrence <quixoticsycophant@gmail.com>
|
572
|
+
|
573
|
+
== License
|
574
|
+
|
575
|
+
Copyright (c) 2009 James M. Lawrence. All rights reserved.
|
576
|
+
|
577
|
+
Permission is hereby granted, free of charge, to any person
|
578
|
+
obtaining a copy of this software and associated documentation files
|
579
|
+
(the "Software"), to deal in the Software without restriction,
|
580
|
+
including without limitation the rights to use, copy, modify, merge,
|
581
|
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
582
|
+
and to permit persons to whom the Software is furnished to do so,
|
583
|
+
subject to the following conditions:
|
584
|
+
|
585
|
+
The above copyright notice and this permission notice shall be
|
586
|
+
included in all copies or substantial portions of the Software.
|
587
|
+
|
588
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
589
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
590
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
591
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
592
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
593
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
594
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
595
|
+
SOFTWARE.
|
58
596
|
|
59
|
-
Pure has been tested on MRI 1.8.6, 1.8.7, 1.9.1 and the latest jruby.
|