pure 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,10 @@
1
1
 
2
2
  = Pure ChangeLog
3
3
 
4
+ == Version 0.2.1
5
+
6
+ * add fun_spec
7
+
4
8
  == Version 0.2.0
5
9
 
6
10
  * more docs
data/MANIFEST CHANGED
@@ -41,6 +41,7 @@ spec/pure_combine_spec.rb
41
41
  spec/pure_def_spec.rb
42
42
  spec/pure_define_method_spec.rb
43
43
  spec/pure_eval_spec.rb
44
+ spec/pure_fun_map_spec.rb
44
45
  spec/pure_fun_spec.rb
45
46
  spec/pure_nested_spec.rb
46
47
  spec/pure_parser_spec.rb
@@ -218,7 +218,7 @@ Or more realistically,
218
218
 
219
219
  require 'pure/dsl'
220
220
 
221
- stats = pure do
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 |*values|
231
- values.inject(0) { |acc, stat| acc + stat.size }
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
- stats.compute { |result|
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 (see example in previous section).
324
+ function definition.
293
325
 
294
326
  == Background
295
327
 
@@ -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
@@ -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] == "fun"
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] == "fun"
71
+ sexp[1][1][1][1] =~ FUN_RE
70
72
  sexp[1][1][1][2][0]
71
73
  else
72
74
  nil
@@ -63,7 +63,7 @@ module Pure
63
63
  elsif sexp[0] == :iter and
64
64
  sexp[1][0] == :call and
65
65
  sexp[1][1] == nil and
66
- sexp[1][2] == :fun
66
+ (sexp[1][2] == :fun || sexp[1][2] == :fun_map)
67
67
  @defs[sexp[1].line] = {
68
68
  :name => :__fun,
69
69
  :code => dup_sexp(sexp)
@@ -67,22 +67,8 @@ module Pure
67
67
  #
68
68
  # See README.rdoc for examples.
69
69
  #
70
- def fun(*args, &block)
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
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Pure
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
@@ -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
- stats = pure do
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 |*values|
15
- values.inject(0) { |acc, stat| acc + stat.size }
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
- stats.compute { |result|
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
+
@@ -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, name|
42
- mod.const_get(name)
41
+ @compiler = names.inject(Object) { |mod, name0|
42
+ mod.const_get(name0)
43
43
  }
44
44
  end
45
45
  const_set(names.last, worker)
@@ -15,10 +15,15 @@ simple_sections = [
15
15
 
16
16
  Jumpstart.doc_to_spec(readme, *simple_sections)
17
17
 
18
- Jumpstart.doc_to_spec(readme, "Dynamic Names") { |expected, actual, index|
19
- [expected, actual].each { |expr|
20
- expr.should match(%r!\A[\d\s]+\Z!)
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.0
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-22 00:00:00 -04:00
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