pure 0.2.0 → 0.2.1
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.
- data/CHANGES.rdoc +4 -0
- data/MANIFEST +1 -0
- data/README.rdoc +37 -5
- data/lib/pure/driver.rb +9 -0
- data/lib/pure/extractor.rb +1 -2
- data/lib/pure/parser/impl/ripper.rb +4 -2
- data/lib/pure/parser/impl/ruby_parser.rb +1 -1
- data/lib/pure/pure_module.rb +54 -16
- data/lib/pure/version.rb +1 -1
- data/spec/fstat_example.rb +29 -6
- data/spec/pure_fun_map_spec.rb +75 -0
- data/spec/pure_spec_base.rb +2 -2
- data/spec/readme_spec.rb +9 -4
- metadata +8 -5
data/CHANGES.rdoc
CHANGED
data/MANIFEST
CHANGED
data/README.rdoc
CHANGED
@@ -218,7 +218,7 @@ Or more realistically,
|
|
218
218
|
|
219
219
|
require 'pure/dsl'
|
220
220
|
|
221
|
-
|
221
|
+
file_stats = pure do
|
222
222
|
files = Dir["*"]
|
223
223
|
|
224
224
|
files.each { |file|
|
@@ -227,12 +227,12 @@ Or more realistically,
|
|
227
227
|
end
|
228
228
|
}
|
229
229
|
|
230
|
-
fun :total_size => files do |*
|
231
|
-
|
230
|
+
fun :total_size => files do |*stats|
|
231
|
+
stats.inject(0) { |acc, stat| acc + stat.size }
|
232
232
|
end
|
233
233
|
end
|
234
234
|
|
235
|
-
|
235
|
+
file_stats.compute { |result|
|
236
236
|
puts result["Rakefile"].size # => 505
|
237
237
|
puts result.total_size # => 39355
|
238
238
|
}
|
@@ -265,6 +265,38 @@ function definition.
|
|
265
265
|
The above is strictly not necessary when the default worker (explained
|
266
266
|
later) is used, however the best strategy is to ignore this detail.
|
267
267
|
|
268
|
+
== Mapping an Enumerable in Parallel
|
269
|
+
|
270
|
+
The convenience method +fun_map+ defines an anonymous pure function
|
271
|
+
which is applied to each element of a given enumerable.
|
272
|
+
|
273
|
+
require 'pure/dsl'
|
274
|
+
|
275
|
+
numbers = pure do
|
276
|
+
fun_map :squares => [3, 4, 5] do |n|
|
277
|
+
n*n
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
p numbers.compute.squares # => [9, 16, 25]
|
282
|
+
|
283
|
+
The example from the "Dynamic Names" section is more easily written
|
284
|
+
with +fun_map+,
|
285
|
+
|
286
|
+
require 'pure/dsl'
|
287
|
+
|
288
|
+
file_stats = pure do
|
289
|
+
fun_map :stats => Dir["*"] do |file|
|
290
|
+
File.stat(file)
|
291
|
+
end
|
292
|
+
|
293
|
+
def total_size(stats)
|
294
|
+
stats.inject(0) { |acc, stat| acc + stat.size }
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
puts file_stats.compute(3).total_size # => 39355
|
299
|
+
|
268
300
|
== Restrictions
|
269
301
|
|
270
302
|
Since the grand scheme of Pure rests upon all functions and function
|
@@ -289,7 +321,7 @@ of course).
|
|
289
321
|
A pure function cannot have default arguments.
|
290
322
|
|
291
323
|
A pure function should not reference variables declared outside the
|
292
|
-
function definition
|
324
|
+
function definition.
|
293
325
|
|
294
326
|
== Background
|
295
327
|
|
data/lib/pure/driver.rb
CHANGED
@@ -9,6 +9,9 @@ module Pure
|
|
9
9
|
driver.define(name.to_sym) { value }
|
10
10
|
}
|
11
11
|
mod.each_function { |name, spec|
|
12
|
+
if elems = spec[:elems]
|
13
|
+
build_fun_map(driver, name, elems)
|
14
|
+
end
|
12
15
|
node = driver.nodes[name]
|
13
16
|
unless node and node.function
|
14
17
|
function = @worker.define_function(spec)
|
@@ -29,5 +32,11 @@ module Pure
|
|
29
32
|
def each_function_name(&block)
|
30
33
|
@driver.nodes.each_key(&block)
|
31
34
|
end
|
35
|
+
|
36
|
+
def build_fun_map(driver, name, elems)
|
37
|
+
elems.each_with_index { |(elem_name, elem), i|
|
38
|
+
driver.define(elem_name) { elem }
|
39
|
+
}
|
40
|
+
end
|
32
41
|
end
|
33
42
|
end
|
data/lib/pure/extractor.rb
CHANGED
@@ -37,8 +37,7 @@ module Pure
|
|
37
37
|
# Use the __fun flag to verify the parser's discovery of a
|
38
38
|
# `fun' call.
|
39
39
|
#
|
40
|
-
extract(mod, :__fun, backtrace).
|
41
|
-
merge(:name => name, :args => args)
|
40
|
+
extract(mod, :__fun, backtrace).merge(:name => name, :args => args)
|
42
41
|
end
|
43
42
|
).merge(
|
44
43
|
:origin => origin,
|
@@ -55,18 +55,20 @@ module Pure
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
FUN_RE = %r!\Afun(_map)?\Z!
|
59
|
+
|
58
60
|
def process_fun(sexp)
|
59
61
|
if sexp[0] == :method_add_block and sexp[1].is_a?(Array)
|
60
62
|
line = (
|
61
63
|
if sexp[1][0] == :command and
|
62
64
|
sexp[1][1].is_a?(Array) and
|
63
|
-
sexp[1][1][1]
|
65
|
+
sexp[1][1][1] =~ FUN_RE
|
64
66
|
sexp[1][1][2][0]
|
65
67
|
elsif sexp[1][0] == :method_add_arg and
|
66
68
|
sexp[1][1].is_a?(Array) and
|
67
69
|
sexp[1][1][0] == :fcall and
|
68
70
|
sexp[1][1][1].is_a?(Array) and
|
69
|
-
sexp[1][1][1][1]
|
71
|
+
sexp[1][1][1][1] =~ FUN_RE
|
70
72
|
sexp[1][1][1][2][0]
|
71
73
|
else
|
72
74
|
nil
|
data/lib/pure/pure_module.rb
CHANGED
@@ -67,22 +67,8 @@ module Pure
|
|
67
67
|
#
|
68
68
|
# See README.rdoc for examples.
|
69
69
|
#
|
70
|
-
def fun(
|
71
|
-
function_str, arg_data = (
|
72
|
-
if args.size == 1
|
73
|
-
arg = args.first
|
74
|
-
if arg.is_a? Hash
|
75
|
-
unless arg.size == 1
|
76
|
-
raise ArgumentError, "`fun' given hash of size != 1"
|
77
|
-
end
|
78
|
-
arg.to_a.first
|
79
|
-
else
|
80
|
-
[arg, []]
|
81
|
-
end
|
82
|
-
else
|
83
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)"
|
84
|
-
end
|
85
|
-
)
|
70
|
+
def fun(arg, &block)
|
71
|
+
function_str, arg_data = parse_fun_arg(arg)
|
86
72
|
arg_names = (
|
87
73
|
if arg_data.is_a? Enumerable
|
88
74
|
arg_data.map { |t| t.to_sym }
|
@@ -98,6 +84,47 @@ module Pure
|
|
98
84
|
nil
|
99
85
|
end
|
100
86
|
|
87
|
+
#
|
88
|
+
# call-seq: fun_map name => enumerable do |elem|
|
89
|
+
# ...
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# Define an anonymous pure function which is applied to each
|
93
|
+
# element of the given enumerable. The pure function _name_
|
94
|
+
# returns the result array.
|
95
|
+
#
|
96
|
+
# See README.rdoc for examples.
|
97
|
+
#
|
98
|
+
def fun_map(arg, &block)
|
99
|
+
function_name, elems = parse_fun_arg(arg)
|
100
|
+
|
101
|
+
function_name = function_name.to_sym
|
102
|
+
elems = elems.to_a
|
103
|
+
|
104
|
+
input_elem_names, output_elem_names = [:input, :output].map { |which|
|
105
|
+
(0...elems.size).map { |index|
|
106
|
+
"__elem_#{which}_#{index}_#{function_name}".to_sym
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
entry = ExtractedFunctions[parser][self]
|
111
|
+
code = Extractor.record_function(self, :fun, :__tmp, [], caller)[:code]
|
112
|
+
entry.delete(:__tmp)
|
113
|
+
|
114
|
+
fun function_name => output_elem_names do |*result|
|
115
|
+
result
|
116
|
+
end
|
117
|
+
entry[function_name][:elems] = input_elem_names.zip(elems)
|
118
|
+
|
119
|
+
output_elem_names.zip(input_elem_names) { |output, input|
|
120
|
+
fun output => input do |*args|
|
121
|
+
block.call(*args)
|
122
|
+
end
|
123
|
+
entry[output][:code] = code
|
124
|
+
}
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
101
128
|
def method_added(function_name) #:nodoc:
|
102
129
|
super
|
103
130
|
if @parsing_active
|
@@ -134,6 +161,17 @@ module Pure
|
|
134
161
|
}
|
135
162
|
end
|
136
163
|
|
164
|
+
def parse_fun_arg(arg) #:nodoc:
|
165
|
+
if arg.is_a? Hash
|
166
|
+
unless arg.size == 1
|
167
|
+
raise ArgumentError, "`fun' given hash of size != 1"
|
168
|
+
end
|
169
|
+
arg.to_a.first
|
170
|
+
else
|
171
|
+
[arg, []]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
137
175
|
# want 'fun' both documented and private; rdoc --all is bad
|
138
176
|
rdoc_fun = :fun
|
139
177
|
private rdoc_fun
|
data/lib/pure/version.rb
CHANGED
data/spec/fstat_example.rb
CHANGED
@@ -1,26 +1,49 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
2
|
|
3
3
|
describe "fstat example" do
|
4
|
-
it "should succeed" do
|
4
|
+
it "should succeed with manual style" do
|
5
5
|
files = Dir["*"]
|
6
6
|
|
7
|
-
|
7
|
+
file_stats = pure do
|
8
8
|
files.each { |file|
|
9
9
|
fun file do
|
10
10
|
File.stat(fun_name.to_s)
|
11
11
|
end
|
12
12
|
}
|
13
13
|
|
14
|
-
fun :total_size => files do |*
|
15
|
-
|
14
|
+
fun :total_size => files do |*stats|
|
15
|
+
stats.inject(0) { |acc, stat| acc + stat.size }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
file_stats.compute(3) { |result|
|
20
20
|
result["Rakefile"].size.should eql(File.stat("Rakefile").size)
|
21
21
|
total = files.inject(0) { |acc, file| acc + File.stat(file).size }
|
22
22
|
result.total_size.should == total
|
23
23
|
}
|
24
24
|
end
|
25
|
-
end
|
26
25
|
|
26
|
+
it "should succeed with fun_map" do
|
27
|
+
file_stats = pure do
|
28
|
+
fun_map :stats_array => Dir["*"] do |file|
|
29
|
+
[file, File.stat(file)]
|
30
|
+
end
|
31
|
+
|
32
|
+
fun :stats => :stats_array do |array|
|
33
|
+
array.inject(Hash.new) { |acc, (name, value)|
|
34
|
+
acc.merge!(name => value)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_size(stats)
|
39
|
+
stats.values.inject(0) { |acc, stat| acc + stat.size }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
file_stats.compute(3) { |result|
|
44
|
+
result.stats["Rakefile"].size.should eql(File.stat("Rakefile").size)
|
45
|
+
total = Dir["*"].inject(0) { |acc, file| acc + File.stat(file).size }
|
46
|
+
result.total_size.should == total
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
describe "pure" do
|
7
|
+
describe "fun_map" do
|
8
|
+
it "should map the given array" do
|
9
|
+
pure do
|
10
|
+
fun_map :squares => 3..5 do |n|
|
11
|
+
n**2
|
12
|
+
end
|
13
|
+
end.compute(3).squares.should == [9, 16, 25]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should map in parallel" do
|
17
|
+
mod = pure do
|
18
|
+
fun_map :sleeper => (1..3).to_a do |n|
|
19
|
+
sleep(0.2)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
epsilon = 0.05 + (RUBY_PLATFORM == "java" ? 99 : 0)
|
23
|
+
mod.compute(1) { |result|
|
24
|
+
Benchmark.measure { result.sleeper }.real.should be_close(0.6, epsilon)
|
25
|
+
}
|
26
|
+
mod.compute(2) { |result|
|
27
|
+
Benchmark.measure { result.sleeper }.real.should be_close(0.4, epsilon)
|
28
|
+
}
|
29
|
+
mod.compute(3) { |result|
|
30
|
+
Benchmark.measure { result.sleeper }.real.should be_close(0.2, epsilon)
|
31
|
+
}
|
32
|
+
mod.compute(4) { |result|
|
33
|
+
Benchmark.measure { result.sleeper }.real.should be_close(0.2, epsilon)
|
34
|
+
}
|
35
|
+
mod.compute(5) { |result|
|
36
|
+
Benchmark.measure { result.sleeper }.real.should be_close(0.2, epsilon)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should accept empty array" do
|
41
|
+
pure do
|
42
|
+
fun_map :squares => [] do |n|
|
43
|
+
n**2
|
44
|
+
end
|
45
|
+
end.compute(3).squares.should == []
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should accept empty enum" do
|
49
|
+
pure do
|
50
|
+
fun_map :squares => 9...9 do |n|
|
51
|
+
n**2
|
52
|
+
end
|
53
|
+
end.compute(3).squares.should == []
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise error when given hash of size != 1" do
|
57
|
+
lambda {
|
58
|
+
pure do
|
59
|
+
fun_map :x => 1, :y => 2 do
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}.should raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise error given more than 1 argument" do
|
66
|
+
lambda {
|
67
|
+
pure do
|
68
|
+
fun_map :x, :y do
|
69
|
+
end
|
70
|
+
end
|
71
|
+
}.should raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
data/spec/pure_spec_base.rb
CHANGED
@@ -38,8 +38,8 @@ module CompilerWorker
|
|
38
38
|
names = name.split("::")
|
39
39
|
worker = Class.new Base do
|
40
40
|
require path
|
41
|
-
@compiler = names.inject(Object) { |mod,
|
42
|
-
mod.const_get(
|
41
|
+
@compiler = names.inject(Object) { |mod, name0|
|
42
|
+
mod.const_get(name0)
|
43
43
|
}
|
44
44
|
end
|
45
45
|
const_set(names.last, worker)
|
data/spec/readme_spec.rb
CHANGED
@@ -15,10 +15,15 @@ simple_sections = [
|
|
15
15
|
|
16
16
|
Jumpstart.doc_to_spec(readme, *simple_sections)
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
sections = ["Dynamic Names", "Mapping an Enumerable in Parallel"]
|
19
|
+
Jumpstart.doc_to_spec(readme, *sections) { |expected, actual, index|
|
20
|
+
if index == 1
|
21
|
+
[expected, actual].each { |expr|
|
22
|
+
expr.should match(%r!\A[\d\s]+\Z!)
|
23
|
+
}
|
24
|
+
else
|
25
|
+
expected.should == actual
|
26
|
+
end
|
22
27
|
}
|
23
28
|
|
24
29
|
Jumpstart.doc_to_spec(readme, "Worker Plugins") { |expected, actual, index|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James M. Lawrence
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-30 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,7 @@ extra_rdoc_files:
|
|
63
63
|
- README.rdoc
|
64
64
|
files:
|
65
65
|
- CHANGES.rdoc
|
66
|
+
- MANIFEST
|
66
67
|
- README.rdoc
|
67
68
|
- Rakefile
|
68
69
|
- devel/jumpstart.rb
|
@@ -104,6 +105,7 @@ files:
|
|
104
105
|
- spec/pure_def_spec.rb
|
105
106
|
- spec/pure_define_method_spec.rb
|
106
107
|
- spec/pure_eval_spec.rb
|
108
|
+
- spec/pure_fun_map_spec.rb
|
107
109
|
- spec/pure_fun_spec.rb
|
108
110
|
- spec/pure_nested_spec.rb
|
109
111
|
- spec/pure_parser_spec.rb
|
@@ -115,7 +117,6 @@ files:
|
|
115
117
|
- spec/readme_spec.rb
|
116
118
|
- spec/splat_spec.rb
|
117
119
|
- spec/worker_spec.rb
|
118
|
-
- MANIFEST
|
119
120
|
has_rdoc: true
|
120
121
|
homepage: http://purefunctional.rubyforge.org
|
121
122
|
licenses: []
|
@@ -129,6 +130,8 @@ rdoc_options:
|
|
129
130
|
- --exclude
|
130
131
|
- CHANGES.rdoc
|
131
132
|
- --exclude
|
133
|
+
- MANIFEST
|
134
|
+
- --exclude
|
132
135
|
- README.rdoc
|
133
136
|
- --exclude
|
134
137
|
- Rakefile
|
@@ -203,6 +206,8 @@ rdoc_options:
|
|
203
206
|
- --exclude
|
204
207
|
- spec/pure_eval_spec.rb
|
205
208
|
- --exclude
|
209
|
+
- spec/pure_fun_map_spec.rb
|
210
|
+
- --exclude
|
206
211
|
- spec/pure_fun_spec.rb
|
207
212
|
- --exclude
|
208
213
|
- spec/pure_nested_spec.rb
|
@@ -224,8 +229,6 @@ rdoc_options:
|
|
224
229
|
- spec/splat_spec.rb
|
225
230
|
- --exclude
|
226
231
|
- spec/worker_spec.rb
|
227
|
-
- --exclude
|
228
|
-
- MANIFEST
|
229
232
|
require_paths:
|
230
233
|
- lib
|
231
234
|
required_ruby_version: !ruby/object:Gem::Requirement
|