fancy 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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
  }