Skip to content

Commit 29e07be

Browse files
committed
fix: Update BoxedFunction to correctly handle _BoxedOperatorDefinition instances
- Modified BoxedFunction to check for _BoxedOperatorDefinition instances in lambda conditions. - Removed the _isLambda property from BoxedOperatorDefinition interface. - Added a test to ensure that lambda functions with list-consuming bodies still broadcast correctly.
1 parent affa0bf commit 29e07be

5 files changed

Lines changed: 4053 additions & 3825 deletions

File tree

CHANGELOG.md

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,75 @@
1717
integer `count` and evaluates to a finite list of `count` copies of `value`.
1818
The 1-arg `Repeat(value)` keeps its existing infinite-sequence semantics. Lazy
1919
collection handlers (`at`, `iterator`, `count`, `isFinite`, `isEmpty`,
20-
`contains`) all branch on arity. To prevent memory blowup, materialization is
21-
capped at 10,000 elements; larger values stay lazy (still accessible
22-
element-by-element via `.at()` / iterator). This cap will switch to
23-
`ce.maxCollectionSize` when that config lands.
20+
`contains`) all branch on arity. Materialization is gated by the new
21+
`ce.maxCollectionSize` (see below); larger values stay lazy (still accessible
22+
element-by-element via `.at()` / iterator).
23+
24+
- **`ce.maxCollectionSize`** — new configurable cap (default `10_000`) on the
25+
number of elements a collection may have when it is materialized into a
26+
concrete `List`. Used by `Repeat`'s arity-2 form and by the eager
27+
`materialize()` path for finite indexed collections; oversize cases leave
28+
the expression in its lazy form rather than building the list. Setter
29+
follows the convention of `iterationLimit` and `recursionLimit`: assigning
30+
`<= 0` or `Infinity` disables the cap. Exposed on the `IComputeEngine`
31+
interface.
32+
33+
- **`Sum(L)` collection-reducer form**`Sum` now accepts a single
34+
collection argument and reduces to the sum of its elements:
35+
`["Sum", ["List", 1, 2, 3, 4, 5]] // ➔ 15`. The big-op form
36+
`Sum(body, [i, a, b], …)` is unchanged. Previously this shape was
37+
silently rewritten by canonicalization to `Reduce(L, "Add", 0)`, which
38+
hid the `Sum` head from the dot-notation serializer; the head is now
39+
preserved so `L.\operatorname{total}` round-trips cleanly with
40+
`latexOptions.dotNotation = true`. The async path now throws
41+
`CancellationError` on signal abort (matching `runAsync`'s contract).
42+
43+
- **`At` extended with boolean-mask and integer-list indices**
44+
`At(L, mask)` where `mask` is a finite collection of `True`/`False`
45+
returns the elements of `L` where the mask is `True`. `At(L, indices)`
46+
where `indices` is a finite collection of integers returns a sublist
47+
picked at those positions; out-of-range positions are filtered. Integer
48+
indices (`At(L, 2)`) and string keys (`At(d, "key")`) work exactly as
49+
before. Signature widened to
50+
`(value: indexed_collection, index: (number|string|indexed_collection)+) -> unknown`.
51+
52+
- **Function-application broadcasting for user-defined lambdas** — when a
53+
user function whose parameters are scalar-typed (no `list`/`collection`/
54+
`tuple` parameter types) is applied to a finite indexed collection, CE
55+
now broadcasts the call elementwise instead of passing the collection as
56+
a single argument. For `ce.assign('f', ce.parse('x \\mapsto x^2 + 1'))`,
57+
the expression `["f", ["List", 1, 2, 3]]` evaluates to `["List", 2, 5, 10]`.
58+
Multi-arg functions broadcast with zip semantics, mixing scalars and
59+
lists naturally (`["h", ["List", 1, 2, 3], 10]` zips the scalar against
60+
the list). Functions whose signature explicitly takes a list (via
61+
`ce.declare(name, '(list<X>) -> Y')`) do **not** broadcast — the inferred
62+
default for `\mapsto` lambdas is scalar parameters, so most user
63+
functions broadcast by default. To opt out, declare an explicit list
64+
parameter type.
65+
66+
- **List type for mixed-kind and mixed-dimension elements**`widen()`
67+
now builds a structural union (e.g. `finite_integer | string`) when the
68+
common supertype would otherwise collapse to a lossy generic category
69+
(`scalar`, `value`, `list`, `tuple`, `dictionary`, …). Consumers can
70+
detect heterogeneous lists by inspecting `expr.type.toString()`:
71+
- `[1, 2, 3]``list<number>` (precise)
72+
- `[1, "hello", 3]``list<finite_integer | string>` (union)
73+
- `[(1,2), (1,2,3)]``list<tuple<finite_integer, finite_integer> | tuple<finite_integer, finite_integer, finite_integer>>` (mixed dimension)
74+
- `[]``list<nothing>` (empty)
75+
76+
Two related fixes landed alongside: `boxed-dictionary.ts`'s `type`
77+
getter and `collectionElementType` in `common/type/utils.ts` previously
78+
template-interpolated type objects as `"[object Object]"` when widen
79+
returned a structural type — both now construct types programmatically.
80+
And `expressionTensorInfo` no longer classifies lists containing
81+
tuples/sets/dictionaries/records/strings as numeric `BoxedTensor`s
82+
(these were being assigned the hardcoded type `list<number^N>`).
83+
84+
- **`ce.box(true)` / `ce.box(false)`** — JS boolean primitives now box to
85+
the `True` / `False` symbols (previously fell through to `Undefined`).
86+
Restores symmetry with `jsValueToExpression` in `math-json/utils.ts`,
87+
which already mapped booleans in this way. Necessary for `At`'s new
88+
boolean-mask path to iterate boolean tensors correctly.
2489

2590
- **`Length` library entry**`Length` was previously only a parse-side head
2691
(produced by `L.\operatorname{count}` dot notation) with no evaluator. It now

0 commit comments

Comments
 (0)