Summary
Phase 2 of #466. Phase 1 (array_mapi, array_reverse, array_find, array_any, array_all, array_flatten, array_sort_by) shipped in v0.0.117 — those are the seven combinators that did not require ability dispatch on the polymorphic element type.
This issue tracks the three array operations that do need ability dispatch and were deferred:
array_sort<T>(arr) -> Array<T> where Ord<T> — sort using the element type's built-in compare instead of a caller-supplied comparator
array_contains<T>(arr, x) -> Bool where Eq<T> — membership test using the element type's built-in eq
array_index_of<T>(arr, x) -> Option<Nat> where Eq<T> — find position using the element type's built-in eq
Why this is a separate piece of work
Phase 1's combinators all take an explicit callback (the predicate, the projection, the comparator), so the iterative WASM loop just invokes the callback via call_indirect exactly like array_filter does. No new infrastructure required.
Phase 2's three operations need to invoke a monomorphized compare$T or eq$T function from inside the iterative loop. The compiler currently dispatches Eq / Ord at operator translation time (when it sees a == b, it looks up T and emits a direct call to eq$T); there is no abstraction for "give me a function handle to the eq/compare for this T at runtime, so a generic loop body can call it later."
Two implementation options:
-
Inline the scan at every call site. When the compiler sees array_contains<T>(arr, x), it emits a fresh inlined loop with a direct call to eq$T. Conceptually clean. Cost: WAT bloat — every call site emits the full loop body, multiplied by the number of distinct element types T.
-
Reify eq$T / compare$T as i32 function handles. Add a code path that takes a monomorphized ability operation and produces a closure-style i32 handle suitable for call_indirect. The array combinator becomes a single iterative loop in the runtime that takes the handle as a parameter, just like array_filter takes a predicate handle today. More architectural work; lower per-call-site cost.
Option 2 is the right answer architecturally and reusable for future ability-dispatched operations. It needs:
- A new internal AST node or helper that takes
(ability_name, type_arg) and produces a function handle expression
- A way to register the monomorphized ability function in the WASM module's function table so its index is allocatable
- Type checker / monomorphizer changes so the compiler knows to instantiate
eq$T / compare$T even when only the array combinator references them
vera/wasm/calls_arrays.py translators that consume the handle the same way _translate_array_filter consumes a predicate
Concrete implementation sketch
def _translate_array_contains(self, arr_arg, key_arg, env):
# Get a function handle for eq$T where T = element type of arr.
t_type = self._infer_concat_elem_type(arr_arg)
eq_handle = self._reify_ability_op("eq", t_type, env) # NEW
# Iterative loop body identical to _translate_array_filter,
# but the predicate compares each element against key_arg using
# call_indirect on eq_handle.
...
The _reify_ability_op helper is the new infrastructure piece. It needs to handle the cases where T is a primitive (use the inline-comparison fast path), an ADT (call eq$ADT$T), a String (call string_eq), etc. — essentially the dispatch table that vera/wasm/operators.py::_translate_adt_eq already has, but exposed as a function-handle producer rather than as direct-call codegen.
Tests
- Conformance: extend
tests/conformance/ch09_array_utilities.vera with phase 2 cases, or add ch09_array_utilities_phase2.vera if cleaner.
- Unit tests in
test_codegen.py::TestArrayUtilities — match the value-based verification style used in phase 1.
- Browser parity in
test_browser.py::TestBrowserArrayUtilities.
Documentation
After phase 2 lands, the deferred-features paragraph at the bottom of spec/09-standard-library.md §9.6.21 and the matching note in SKILL.md should be removed.
Priority
Lower urgency than phase 1 because the combinators it adds have explicit-callback alternatives that already work (array_sort_by, array_any with an equality predicate, array_find with an equality predicate). The ergonomic win is genuine but smaller than mapi / flatten / etc. were.
Summary
Phase 2 of #466. Phase 1 (
array_mapi,array_reverse,array_find,array_any,array_all,array_flatten,array_sort_by) shipped in v0.0.117 — those are the seven combinators that did not require ability dispatch on the polymorphic element type.This issue tracks the three array operations that do need ability dispatch and were deferred:
array_sort<T>(arr) -> Array<T> where Ord<T>— sort using the element type's built-incompareinstead of a caller-supplied comparatorarray_contains<T>(arr, x) -> Bool where Eq<T>— membership test using the element type's built-ineqarray_index_of<T>(arr, x) -> Option<Nat> where Eq<T>— find position using the element type's built-ineqWhy this is a separate piece of work
Phase 1's combinators all take an explicit callback (the predicate, the projection, the comparator), so the iterative WASM loop just invokes the callback via
call_indirectexactly likearray_filterdoes. No new infrastructure required.Phase 2's three operations need to invoke a monomorphized
compare$Toreq$Tfunction from inside the iterative loop. The compiler currently dispatchesEq/Ordat operator translation time (when it seesa == b, it looks up T and emits a direct call toeq$T); there is no abstraction for "give me a function handle to the eq/compare for this T at runtime, so a generic loop body can call it later."Two implementation options:
Inline the scan at every call site. When the compiler sees
array_contains<T>(arr, x), it emits a fresh inlined loop with a direct call toeq$T. Conceptually clean. Cost: WAT bloat — every call site emits the full loop body, multiplied by the number of distinct element types T.Reify
eq$T/compare$Tas i32 function handles. Add a code path that takes a monomorphized ability operation and produces a closure-style i32 handle suitable forcall_indirect. The array combinator becomes a single iterative loop in the runtime that takes the handle as a parameter, just likearray_filtertakes a predicate handle today. More architectural work; lower per-call-site cost.Option 2 is the right answer architecturally and reusable for future ability-dispatched operations. It needs:
(ability_name, type_arg)and produces a function handle expressioneq$T/compare$Teven when only the array combinator references themvera/wasm/calls_arrays.pytranslators that consume the handle the same way_translate_array_filterconsumes a predicateConcrete implementation sketch
The
_reify_ability_ophelper is the new infrastructure piece. It needs to handle the cases where T is a primitive (use the inline-comparison fast path), an ADT (calleq$ADT$T), a String (callstring_eq), etc. — essentially the dispatch table thatvera/wasm/operators.py::_translate_adt_eqalready has, but exposed as a function-handle producer rather than as direct-call codegen.Tests
tests/conformance/ch09_array_utilities.verawith phase 2 cases, or addch09_array_utilities_phase2.veraif cleaner.test_codegen.py::TestArrayUtilities— match the value-based verification style used in phase 1.test_browser.py::TestBrowserArrayUtilities.Documentation
After phase 2 lands, the deferred-features paragraph at the bottom of
spec/09-standard-library.md§9.6.21 and the matching note inSKILL.mdshould be removed.Priority
Lower urgency than phase 1 because the combinators it adds have explicit-callback alternatives that already work (
array_sort_by,array_anywith an equality predicate,array_findwith an equality predicate). The ergonomic win is genuine but smaller thanmapi/flatten/ etc. were.