fancy 0.3.2 → 0.3.3

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.
Files changed (98) hide show
  1. data/README.md +4 -1
  2. data/Rakefile +8 -0
  3. data/bin/fyi +25 -20
  4. data/bin/ifancy +39 -5
  5. data/{extconf.rb → boot/extconf.rb} +1 -1
  6. data/boot/fancy_ext/block_env.rb +0 -14
  7. data/boot/fancy_ext/kernel.rb +6 -2
  8. data/boot/rbx-compiler/parser/fancy_parser.bundle +0 -0
  9. data/examples/actor.fy +37 -0
  10. data/examples/armstrong_numbers.fy +1 -1
  11. data/examples/curl_async.fy +37 -0
  12. data/examples/echo.fy +1 -1
  13. data/examples/factorial.fy +1 -1
  14. data/examples/future_composition.fy +20 -0
  15. data/examples/game_of_life.fy +2 -2
  16. data/examples/person.fy +4 -8
  17. data/examples/rbx/blocks.fy +1 -1
  18. data/examples/return.fy +1 -1
  19. data/examples/struct.fy +9 -0
  20. data/lib/argv.fy +2 -2
  21. data/lib/array.fy +157 -0
  22. data/lib/block.fy +18 -1
  23. data/lib/boot.fy +5 -1
  24. data/lib/compiler/ast/class_def.fy +1 -1
  25. data/lib/compiler/ast/identifier.fy +2 -2
  26. data/lib/compiler/ast/message_send.fy +2 -2
  27. data/lib/compiler/ast/method_def.fy +2 -2
  28. data/lib/compiler/ast/try_catch.fy +5 -1
  29. data/lib/documentation.fy +1 -1
  30. data/lib/enumerable.fy +3 -7
  31. data/lib/enumerator.fy +77 -0
  32. data/lib/false_class.fy +52 -0
  33. data/lib/fancy_spec.fy +40 -12
  34. data/lib/fdoc.fy +2 -2
  35. data/lib/file.fy +8 -1
  36. data/lib/future.fy +23 -2
  37. data/lib/iteration.fy +60 -0
  38. data/lib/main.fy +4 -4
  39. data/lib/nil_class.fy +14 -22
  40. data/lib/number.fy +51 -0
  41. data/lib/object.fy +126 -43
  42. data/lib/package/installer.fy +1 -1
  43. data/lib/parser/ext/lexer.lex +6 -1
  44. data/lib/parser/ext/parser.y +18 -0
  45. data/lib/parser/methods.fy +20 -2
  46. data/lib/proxy.fy +20 -3
  47. data/lib/rbx.fy +0 -1
  48. data/lib/rbx/array.fy +4 -138
  49. data/lib/rbx/block.fy +25 -1
  50. data/lib/rbx/class.fy +21 -0
  51. data/lib/rbx/exception.fy +1 -0
  52. data/lib/rbx/fiber.fy +1 -0
  53. data/lib/rbx/file.fy +8 -0
  54. data/lib/rbx/integer.fy +0 -8
  55. data/lib/rbx/method.fy +34 -7
  56. data/lib/rbx/no_method_error.fy +8 -1
  57. data/lib/rbx/object.fy +3 -32
  58. data/lib/rbx/range.fy +13 -1
  59. data/lib/rbx/regexp.fy +3 -0
  60. data/lib/rbx/string.fy +6 -1
  61. data/lib/rbx/system.fy +20 -2
  62. data/lib/set.fy +2 -2
  63. data/lib/string.fy +1 -1
  64. data/lib/struct.fy +15 -12
  65. data/lib/symbol.fy +2 -2
  66. data/lib/true_class.fy +16 -20
  67. data/lib/version.fy +1 -1
  68. data/tests/argv.fy +1 -0
  69. data/tests/array.fy +33 -2
  70. data/tests/block.fy +44 -0
  71. data/tests/class.fy +102 -88
  72. data/tests/control_flow.fy +131 -8
  73. data/tests/enumerator.fy +85 -0
  74. data/tests/exception.fy +13 -13
  75. data/tests/file.fy +4 -13
  76. data/tests/future.fy +26 -0
  77. data/tests/method.fy +83 -72
  78. data/tests/nil_class.fy +20 -13
  79. data/tests/number.fy +16 -9
  80. data/tests/object.fy +39 -20
  81. data/tests/string.fy +7 -0
  82. data/tests/true_class.fy +4 -4
  83. data/tools/fancy-mode.el +1 -1
  84. metadata +15 -20
  85. data/boot/compiler/parser/ext/fancy_parser.bundle +0 -0
  86. data/boot/rbx-compiler/parser/Makefile +0 -162
  87. data/boot/rbx-compiler/parser/lexer.c +0 -2316
  88. data/boot/rbx-compiler/parser/lexer.h +0 -315
  89. data/boot/rbx-compiler/parser/parser.c +0 -3105
  90. data/boot/rbx-compiler/parser/parser.h +0 -114
  91. data/lib/lazy_array.fy +0 -23
  92. data/lib/parser/ext/Makefile +0 -162
  93. data/lib/parser/ext/fancy_parser.bundle +0 -0
  94. data/lib/parser/ext/lexer.c +0 -2360
  95. data/lib/parser/ext/lexer.h +0 -315
  96. data/lib/parser/ext/parser.c +0 -3382
  97. data/lib/parser/ext/parser.h +0 -118
  98. data/lib/rbx/false_class.fy +0 -58
data/README.md CHANGED
@@ -133,9 +133,12 @@ Ruby method invocation supports passing a block variable to Ruby as a proc.
133
133
  - Calling, using and extending arbitrary Ruby classes and methods
134
134
  (including C-extensions), as well as passing blocks and splat
135
135
  arguments to Ruby methods.
136
+ - Futures (`future = object @ message`)
137
+ - Async message sends (`object @@ message`)
138
+
136
139
 
137
140
  ##What's still missing?
138
- - Concurrency stuff (Actor-model based concurrency, Futures etc)
141
+ - Some concurrency stuff (e.g. language syntax for actors)
139
142
  - Some more advanced stuff, e.g. runtime inspection of method calls
140
143
  via MethodContext etc. (saved for later)
141
144
 
data/Rakefile CHANGED
@@ -255,6 +255,14 @@ task :test do
255
255
  *Dir.glob(_("tests/*.fy"))
256
256
  end
257
257
 
258
+ task :tests do
259
+ task(:test).invoke
260
+ end
261
+
262
+ task "tests/" do
263
+ task(:test).invoke
264
+ end
265
+
258
266
  task :bootstrap => ["compiler:bootstrap"]
259
267
 
260
268
  task :default => [:bootstrap_if_needed, :compile]
data/bin/fyi CHANGED
@@ -1,25 +1,30 @@
1
1
  #!/usr/bin/env fancy
2
2
  # -*- fancy -*-
3
3
 
4
- if: (ARGV[1]) then: |ident| {
5
- "Documentation for: '#{ident}' :" println;
6
- if: (ident includes?: "#") then: {
7
- parts = ident split: "#"
8
- obj = parts[0] eval
9
- if: (parts[1]) then: |method_name| {
10
- method = obj instance_method: $ parts[1]
11
- method documentation println
12
- System exit
4
+ try {
5
+ if: (ARGV[1]) then: |ident| {
6
+ documentation = nil
7
+ if: (ident includes?: "#") then: {
8
+ parts = ident split: "#"
9
+ obj = parts[0] eval
10
+ if: (parts[1]) then: |method_name| {
11
+ method = obj instance_method: $ parts[1]
12
+ documentation = method documentation
13
+ }
13
14
  }
15
+ # just print documentation for class here
16
+ obj = ident eval
17
+ documentation = obj documentation
18
+ "Documentation for: '#{ident}':" println
19
+ documentation println
20
+ } else: {
21
+ ["Prints the documentation string for a given object or one if its methods",
22
+ "Usage:",
23
+ "fyi [ObjectIdentifier]",
24
+ "fyi [ObjectIdentifier]#[MethodName]",
25
+ "",
26
+ "Example: fyi Array#each:"] println
14
27
  }
15
- # just print documentation for class here
16
- obj = ident eval
17
- obj documentation println
18
- } else: {
19
- ["Prints the documentation string for a given object or one if its methods",
20
- "Usage:",
21
- "fyi [ObjectIdentifier]",
22
- "fyi [ObjectIdentifier]#[MethodName]",
23
- "",
24
- "Example: fyi Array#each:"] println
25
- }
28
+ } catch Exception => e {
29
+ "ERROR: #{e message}" println
30
+ }
data/bin/ifancy CHANGED
@@ -6,6 +6,9 @@ require("readline")
6
6
  ["Welcome to the (still very simple) Fancy REPL",
7
7
  "Fancy " ++ FANCY_VERSION] println
8
8
 
9
+ HISTORY_FILE = File expand_path("~/.fancy_history")
10
+ HISTORY = []
11
+
9
12
  ARGV rest each: |file| {
10
13
  "LOADING: " ++ file println
11
14
  require: file
@@ -14,26 +17,55 @@ ARGV rest each: |file| {
14
17
  # handle SIGINT
15
18
  trap("INT") {
16
19
  "Quitting." println
20
+ save_history
17
21
  System exit
18
22
  }
19
23
 
20
24
  Console newline;
21
25
 
22
26
  def double_or_empty?: line {
23
- (line =~ /^\s*$/) or: (Readline HISTORY to_a[-2] == line)
27
+ (line =~ /^\s*$/) or: (HISTORY [-2] == line)
28
+ }
29
+
30
+ def load_history {
31
+ if: (File exists?: HISTORY_FILE) then: {
32
+ File open: HISTORY_FILE modes: ['read] with: |f| {
33
+ f readlines each: |l| {
34
+ l = l strip()
35
+ Readline HISTORY <<(l)
36
+ HISTORY << l
37
+ }
38
+ }
39
+ } else: {
40
+ File touch: HISTORY_FILE
41
+ }
24
42
  }
25
43
 
44
+ def save_history {
45
+ puts("saving history")
46
+ unless: @history_saved do: {
47
+ File open: HISTORY_FILE modes: ['write] with: |f| {
48
+ HISTORY each: |l| {
49
+ f writeln: l
50
+ }
51
+ }
52
+ @history_saved = true
53
+ }
54
+ }
55
+
56
+ load_history
26
57
 
27
58
  try {
28
59
  bnd = binding()
29
60
 
30
- { Readline readline(">> ", true) } while_do: |line| {
31
-
32
- double_or_empty?: line . if_true: {
61
+ while: { Readline readline(">> ", true) } do: |line| {
62
+ HISTORY << line
63
+ if: (double_or_empty?: line) then: {
33
64
  Readline::HISTORY pop()
65
+ HISTORY pop()
34
66
  }
35
67
 
36
- line =~ /^\s*$/ if_false: {
68
+ unless: (line =~ /^\s*$/) do: {
37
69
  try {
38
70
  Fancy eval: line binding: bnd . inspect println
39
71
  } catch Exception => e {
@@ -41,6 +73,8 @@ try {
41
73
  }
42
74
  }
43
75
  }
76
+ save_history
44
77
  } catch Interrupt => e {
78
+ save_history
45
79
  System exit
46
80
  }
@@ -2,6 +2,6 @@ base = File.dirname(__FILE__)
2
2
  puts "Fancy hasn't been bootstrapped yet. Doing that now.\n\n"
3
3
  File.open("Makefile", "w") do |f|
4
4
  f.puts "install:"
5
- f.puts " rbx -S rake clean && rbx -S rake"
5
+ f.puts " cd ../ && rbx -S rake clean && rbx -S rake"
6
6
  end
7
7
  exit 0
@@ -2,12 +2,6 @@
2
2
  Block = Rubinius::BlockEnvironment
3
3
 
4
4
  class Block
5
- define_method("while_true:") do |block|
6
- while tmp = self.call
7
- block.call(tmp)
8
- end
9
- end
10
-
11
5
  # call without arguments
12
6
  alias_method :":call", :call
13
7
 
@@ -22,12 +16,4 @@ class Block
22
16
  call *args
23
17
  end
24
18
  end
25
-
26
- define_method("call_with_receiver:") do |obj|
27
- call_under obj, method.scope
28
- end
29
-
30
- define_method("call:with_receiver:") do |args, obj|
31
- call_under obj, method.scope, *args
32
- end
33
19
  end
@@ -1,6 +1,10 @@
1
1
  module Kernel
2
-
3
- alias_method :":metaclass", :metaclass
2
+ begin
3
+ alias_method ":metaclass", :metaclass
4
+ rescue
5
+ alias_method :":metaclass", :singleton_class
6
+ alias_method :metaclass, :singleton_class
7
+ end
4
8
 
5
9
  def fancy_require(file, compile = false)
6
10
  if compile
data/examples/actor.fy ADDED
@@ -0,0 +1,37 @@
1
+ require("actor")
2
+ class Actor {
3
+ alias_method: '! for_ruby: '<<
4
+ }
5
+
6
+ # Execute with Rubinius: rbx ex1.rb
7
+
8
+ def error_loop: block{
9
+ try {
10
+ loop: block
11
+ } catch Exception => ex {
12
+ ex message println
13
+ ex backtrace join: "\n"
14
+ }
15
+ }
16
+
17
+ pong = nil
18
+ ping = Actor spawn() {
19
+ error_loop: {
20
+ count = Actor receive()
21
+ "." print
22
+ { return count println } if: (count > 1000)
23
+ pong ! (count + 1)
24
+ }
25
+ }
26
+ pong = Actor.spawn() {
27
+ error_loop: {
28
+ count = Actor receive()
29
+ "-" print
30
+ { return count println } if: (count > 1000)
31
+ ping ! (count + 1)
32
+ }
33
+ }
34
+ ping ! 1
35
+
36
+ # Let the actors process while the main thread sleeps...
37
+ Thread sleep: 1
@@ -28,6 +28,6 @@ class Fixnum {
28
28
  }
29
29
 
30
30
  # output alls Armstrong Numbers between 0 and 10000
31
- 0 upto: 10000 do_each: |i| {
31
+ 0 upto: 10000 do: |i| {
32
32
  { i println } if: $ i armstrong?
33
33
  }
@@ -0,0 +1,37 @@
1
+ def async curl: url {
2
+ out = System pipe: "curl #{url}"
3
+ return out read # return string of html
4
+ }
5
+
6
+ def async curl_urls: urls {
7
+ results = []
8
+ urls each: |url| {
9
+ # await means it will suspend the current context until the curl: has finished
10
+ # and will resume with the line below
11
+ data = await curl: url
12
+ results << data
13
+ }
14
+ return results
15
+ }
16
+
17
+ URLS = ["http://www.backtype.com", "http://www.fancy-lang.org", "http://tech.backtype.com"]
18
+
19
+ # usage (runs it asynchronously and returns a future that will hold the data when its done)
20
+ f = curl_urls: URLS
21
+
22
+ # do something else...
23
+ # then after some time access the data:
24
+
25
+ f value each: |html| {
26
+ html println
27
+ }
28
+
29
+ # or hook it up with something to do when its done:
30
+ curl_urls: URLS && |data| {
31
+ data each: |html| {
32
+ html println
33
+ }
34
+ }
35
+
36
+ # shorter version of above:
37
+ curl_urls: URLS && @{each: 'println}
data/examples/echo.fy CHANGED
@@ -1,7 +1,7 @@
1
1
  # echo.fy
2
2
  # Outputs contents of files
3
3
 
4
- ARGV[1] if_do: |filename| {
4
+ ARGV[1] if_true: |filename| {
5
5
  try {
6
6
  File open: filename modes: ['read] with: |f| {
7
7
  until: { f eof? } do: {
@@ -7,6 +7,6 @@ class Fixnum {
7
7
  }
8
8
  }
9
9
 
10
- 1 upto: 10 do_each: |i| {
10
+ 1 upto: 10 do: |i| {
11
11
  i to_s ++ "! = " ++ (i factorial) println
12
12
  }
@@ -0,0 +1,20 @@
1
+ # example of composing futures:
2
+
3
+ def do_large_computation: x {
4
+ x upto: (x ** x)
5
+ }
6
+
7
+ # Future#&& takes a Block (or a Callable - something that implements 'call:)
8
+ # and creates a new Future that executes the given Block with the value of the first Future
9
+ # when it has finished its computation. This makes pipelining tasks easy.
10
+
11
+ f = self @ do_large_computation: 5 && @{select: 'even?} && @{inspect println}
12
+ "computing .. " println
13
+ f value
14
+
15
+ #the above is the same as:
16
+ # f = self @ do_large_computation: 5
17
+ # f = f when_done: @{select: 'even?}
18
+ # f = f when_done: @{inspect println}
19
+ # "computing .. " println
20
+ # f value
@@ -72,7 +72,7 @@ class World {
72
72
  def was_alive?: pos {
73
73
  "Indicates, if a cell ([row,column]) was alive in the last generation."
74
74
 
75
- @last_alive[pos[0]] if_do: |row| {
75
+ @last_alive[pos[0]] if_true: |row| {
76
76
  row[pos[1]] == 1
77
77
  }
78
78
  }
@@ -115,7 +115,7 @@ class World {
115
115
  column = pos[1]
116
116
 
117
117
  neighbors = @offsets map: |o| {
118
- @matrix[row + (o[0])] if_do: |r| {
118
+ @matrix[row + (o[0])] if_true: |r| {
119
119
  r[column + (o[1])]
120
120
  }
121
121
  }
data/examples/person.fy CHANGED
@@ -2,9 +2,8 @@
2
2
  # Annotated example of fancy's classes mechanism
3
3
 
4
4
  class City {
5
- read_slots: ['city]
6
- def initialize: name {
7
- @name = name
5
+ read_slots: ['name]
6
+ def initialize: @name {
8
7
  }
9
8
 
10
9
  def to_s {
@@ -24,10 +23,7 @@ class Person {
24
23
  # method but having initialize: replaced by new:.
25
24
  # So in this case: Person##new:age:city:
26
25
  # which calls this instance method internally
27
- def initialize: name age: age city: city {
28
- @name = name
29
- @age = age
30
- @city = city
26
+ def initialize: @name age: @age city: @city {
31
27
  }
32
28
 
33
29
  def go_to: city {
@@ -50,7 +46,7 @@ class Person {
50
46
  }
51
47
 
52
48
  def to_s {
53
- "Person: " ++ @name ++ ", " ++ @age ++ " years old, living in " ++ @city
49
+ "Person: #{@name}, #{@age} years old, living in #{@city}"
54
50
  }
55
51
  }
56
52
 
@@ -2,7 +2,7 @@
2
2
  i println
3
3
  }
4
4
 
5
- 1 upto: 4 do_each: |i| {
5
+ 1 upto: 4 do: |i| {
6
6
  i println
7
7
  }
8
8
 
data/examples/return.fy CHANGED
@@ -1,5 +1,5 @@
1
1
  def foo: block {
2
- 1 upto: 10 do_each: |i| {
2
+ 1 upto: 10 do: |i| {
3
3
  val = block call: [i]
4
4
  if: (block call: [i]) then: {
5
5
  return i # non-local return from "foo:"
@@ -0,0 +1,9 @@
1
+ S = Struct new: ['foo, 'bar, 'baz]
2
+ s = S new: (1,2,3)
3
+ s println
4
+
5
+ s foo: 10
6
+ s bar: 20
7
+ s baz: 30
8
+
9
+ s println
data/lib/argv.fy CHANGED
@@ -1,9 +1,9 @@
1
1
  def ARGV for_option: op_name do: block {
2
2
  "Runs a given block if an option is in ARGV."
3
3
 
4
- ARGV index: op_name . if_do: |idx| {
4
+ ARGV index: op_name . if_true: |idx| {
5
5
  if: (block argcount > 0) then: {
6
- ARGV[idx + 1] if_do: |arg| {
6
+ ARGV[idx + 1] if_true: |arg| {
7
7
  block call: [arg]
8
8
  ARGV remove_at: idx
9
9
  ARGV remove_at: idx
data/lib/array.fy CHANGED
@@ -7,6 +7,30 @@ class Array {
7
7
 
8
8
  include: FancyEnumerable
9
9
 
10
+ def Array new: size {
11
+ "Creates a new Array with a given size (default value is nil)."
12
+
13
+ Array new: size with: nil
14
+ }
15
+
16
+ def clone {
17
+ "Clones (shallow copy) the Array."
18
+ new = []
19
+ each: |x| {
20
+ new << x
21
+ }
22
+ new
23
+ }
24
+
25
+ def append: arr {
26
+ "Appends another Array onto this one."
27
+
28
+ arr each: |x| {
29
+ self << x
30
+ }
31
+ self
32
+ }
33
+
10
34
  def [] index {
11
35
  """
12
36
  Given an Array of 2 Numbers, it returns the sub-array between the
@@ -47,6 +71,42 @@ class Array {
47
71
  from: 1 to: -1
48
72
  }
49
73
 
74
+ def each: block {
75
+ """
76
+ @block @Block@ to be called for each element in @self.
77
+ @return Return value of calling @block on the last item in @self.
78
+
79
+ Calls a given @Block@ with each element in the @Array@.
80
+ """
81
+
82
+ try {
83
+ size times: |i| {
84
+ try {
85
+ block call: [at: i]
86
+ } catch (Fancy NextIteration) => ex {
87
+ }
88
+ }
89
+ self
90
+ } catch (Fancy BreakIteration) => ex {
91
+ ex return_value
92
+ }
93
+ }
94
+
95
+ def each_with_index: block {
96
+ """
97
+ @block @Block@ to be called with each element and its inde in the @Array@.
98
+
99
+ Iterate over all elements in Array. Calls a given Block with each element and its index.
100
+ """
101
+
102
+ i = 0
103
+ each: |x| {
104
+ block call: [x, i]
105
+ i = i + 1
106
+ }
107
+ nil
108
+ }
109
+
50
110
  def =? other {
51
111
  """
52
112
  @other Other @Array@ to compare this one to.
@@ -93,6 +153,7 @@ class Array {
93
153
  return x
94
154
  }
95
155
  }
156
+ nil
96
157
  }
97
158
 
98
159
  def values_at: idx_arr {
@@ -219,9 +280,105 @@ class Array {
219
280
  0 upto: (size - 1)
220
281
  }
221
282
 
283
+ def indices_of: item {
284
+ """
285
+ @item Item/Value for which a list of indices is requested within an @Array@.
286
+ @return @Array@ of all indices for a given value within an @Array@ (possibly empty).
287
+
288
+ Returns an Array of all indices of this item. Empty Array if item does not occur.
289
+ """
290
+
291
+ tmp = []
292
+ each_with_index: |obj, idx| {
293
+ if: (item == obj) then: {
294
+ tmp << idx
295
+ }
296
+ }
297
+ tmp
298
+ }
299
+
300
+ def from: from to: to {
301
+ """
302
+ @from Start index for sub-array.
303
+ @to End index ofr sub-array.
304
+
305
+ Returns sub-array starting at from: and going to to:
306
+ """
307
+
308
+ if: (from < 0) then: {
309
+ from = size + from
310
+ }
311
+ if: (to < 0) then: {
312
+ to = size + to
313
+ }
314
+ subarr = []
315
+ from upto: to do: |i| {
316
+ subarr << (at: i)
317
+ }
318
+ subarr
319
+ }
320
+
321
+ def select: block {
322
+ """
323
+ @block Predicate @Block@ to be used as filter.
324
+ @return @Array@ of all the elements for which @block doesn't yield @false or @nil.
325
+
326
+ Returns a new Array with all the elements in self that yield a
327
+ true-ish value when called with the given Block.
328
+ """
329
+
330
+ tmp = []
331
+ each: |x| {
332
+ if: (block call: [x]) then: {
333
+ tmp << x
334
+ }
335
+ }
336
+ return tmp
337
+ }
338
+
339
+ def select_with_index: block {
340
+ """
341
+ Same as select:, just gets also called with an additional argument
342
+ for each element's index value.
343
+ """
344
+
345
+ tmp = []
346
+ each_with_index: |obj idx| {
347
+ if: (block call: [obj, idx]) then: {
348
+ tmp << [obj, idx]
349
+ }
350
+ }
351
+ tmp
352
+ }
353
+
222
354
  def Array === object {
355
+ """
356
+ @object Object to match @self against.
357
+ @return @nil, if no match, matched values (in an @Array) otherwise.
358
+
359
+ Matches an @Array against another object.
360
+ """
361
+
223
362
  if: (object is_a?: Array) then: {
224
363
  return [object] + object
225
364
  }
226
365
  }
366
+
367
+ def sum {
368
+ """
369
+ Calculates the sum of all the elements in the Enumerable
370
+ (assuming them to be Numbers (implementing '+' & '*')).
371
+ """
372
+
373
+ reduce: |x y| { x + y } init_val: 0
374
+ }
375
+
376
+ def product {
377
+ """
378
+ Calculates the product of all the elements in the Enumerable
379
+ (assuming them to be Numbers (implementing '+' & '*')).
380
+ """
381
+
382
+ reduce: |x y| { x * y } init_val: 1
383
+ }
227
384
  }