Phase 15. List aggregates and HOF (min, max, sum, in, map, filter, reduce)
| Field | Value |
|---|---|
| MEP | MEP-56 §Phases |
| Status | LANDED |
| Started | 2026-05-29 12:01 (GMT+7) |
| Landed | 2026-05-29 12:01 (GMT+7) |
| Tracking issue | none |
| Tracking PR | #22510 |
| Commit | 0732df08e0 |
Gate
TestPhase15ListAggHOF in transpiler3/ruby/build/phase15_test.go: five subtests (list_min_max_sum, list_in, list_map, list_filter, list_reduce). Each subtest compiles a Mochi program that hits one Enumerable family (e.g. reduce([1,2,3,4], plus, 0) must print 10), then runs the emitted .rb under the resolved Ruby toolchain with -I mochi-runtime/lib and diffs stdout. The HOF cases (list_map, list_filter, list_reduce) pass a named Mochi function (dbl, is_even, plus) as the callback, so the gate also covers Phase 7's closure-as-lambda machinery flowing through .call.
Lowering decisions
List aggregates lower to Ruby Enumerable method calls; HOF builtins lower to a RawExpr carrying a Ruby block that invokes the callback via .call (transpiler3/ruby/lower/lower.go lines 874 to 938):
aotir.ListMinExprtoMethodCall{Method: "min"}(lines 874 to 879). RubyEnumerable#minreturnsnilon an empty array; the gate exercises a non-empty list, matching Mochi's documented "undefined on empty" formin.aotir.ListMaxExprtoMethodCall{Method: "max"}(lines 880 to 885).aotir.ListContainsExpr(the desugared form ofx in xs) toMethodCall{Method: "include?", UseParens: true}(lines 886 to 895). RubyArray#include?does linear==scan, matchinginsemantics.aotir.ListSumExprtoMethodCall{Method: "sum"}(lines 896 to 901). RubyEnumerable#sumis C-level since 2.4 and works onIntegerandFloatlists; the gate's[3,1,4,1,5,9,2,6]yields31.aotir.ListMapExprtoRawExprrenderinglist.map { |__x| (fn).call(__x) }(lines 902 to 912). The wrapped(fn).call(__x)form is essential: Mochifnis a RubyProc(lambda) built by Phase 7, so a barefn(__x)would parse as a method invocation, not a Proc call.aotir.ListFilterExprtoRawExprrenderinglist.select { |__x| (fn).call(__x) }(lines 913 to 923).selectis preferred overfilterbecauseselectis the historical Ruby spelling and is present on every supported runtime;Array#filteris only an alias added in 2.6.aotir.ListFoldlExpr(Mochi'sreduce(xs, fn, init)) toRawExprrenderinglist.inject(init) { |__acc, __x| (fn).call(__acc, __x) }(lines 924 to 938).inject(notreduce) is the historical spelling and is identical in Ruby. The seed is passed positionally so it stays distinct from the per-element accumulator.
The __x and __acc block parameters use the __-prefix convention reserved by the lowerer to avoid colliding with any Mochi-level identifier (Phase 0 §lowering convention).
Files changed
| File | Purpose |
|---|---|
transpiler3/ruby/lower/lower.go | Six aotir.List*Expr cases lower to Enumerable methods or Ruby blocks calling the Mochi Proc via .call, lines 874 to 938 |
transpiler3/ruby/build/phase15_test.go | TestPhase15ListAggHOF with 5 subtests |
Test set
TestPhase15ListAggHOF/list_min_max_sum,list_in,list_map,list_filter,list_reduce.
Closeout notes
Phase 15 landed on CRuby 4.0 (Homebrew). The decision to render (fn).call(__x) instead of fn.call(__x) is defensive: when fn is a RawExpr carrying e.g. lambda { ... } syntax, the outer parens stop Ruby from parsing the .call as a method on the literal closing brace. Picking inject over reduce and select over filter deliberately maximises compatibility back to older Rubies (mruby 4 is targeted by Phase 27) without giving anything up on modern CRuby.