diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index fdd0b9dc..56fbec1e 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -1230,9 +1230,9 @@ object Val { */ private final class MappedArr( pos0: Position, - private var source: Arr, - private var func: Func, - private var callPos: Position, + private[Val] var source: Arr, + private[Val] var func: Func, + private[Val] var callPos: Position, private var ev: EvalScope) extends LazyViewArr(pos0, source.length) { @@ -1247,6 +1247,35 @@ object Val { } } + /** + * Fused view for std.map(f, std.map(g, arr)). Applies both functions in sequence without an + * intermediate MappedArr cache layer, eliminating one allocation + indirection per element. + */ + private final class ComposedMappedArr( + pos0: Position, + private var source: Arr, + private var outerFunc: Func, + private var innerFunc: Func, + private var outerCallPos: Position, + private var innerCallPos: Position, + private var ev: EvalScope) + extends LazyViewArr(pos0, source.length) { + + protected def computeAt(index: Int): Val = { + val inner = innerFunc.apply1(source.eval(index), innerCallPos)(ev, TailstrictModeDisabled) + outerFunc.apply1(inner, outerCallPos)(ev, TailstrictModeDisabled) + } + + override protected def releaseCapturedState(): Unit = { + source = null + outerFunc = null + innerFunc = null + outerCallPos = null + innerCallPos = null + ev = null + } + } + /** * Lazy view for std.mapWithIndex(func, arr). * @@ -1616,7 +1645,20 @@ object Val { i += 1 } Arr(pos, result) - } else new MappedArr(pos, source, func, callPos, ev) + } else + source match { + case inner: MappedArr if inner.source != null => + new ComposedMappedArr( + pos, + inner.source, + func, + inner.func, + callPos, + inner.callPos, + ev + ) + case _ => new MappedArr(pos, source, func, callPos, ev) + } def mappedWithIndex( pos: Position, diff --git a/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet b/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet index 3bd1a220..73f99dc8 100644 --- a/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet +++ b/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet @@ -8,10 +8,17 @@ local made = std.makeArray(100000, function(i) if i == 0 then error 'forced make local chain = std.map(function(x) x + 1, std.map(function(x) x * 2, std.makeArray(50000, function(i) i))); local withIdx = std.mapWithIndex(function(i, x) i * 10 + x, std.range(1, 3)); +// ComposedMappedArr: fused map chain with full materialization, repeated access, reverse +local fused = std.map(function(x) x * x, std.map(function(x) x + 1, std.range(0, 4))); + std.assertEqual(mapped[1], 11) && std.assertEqual(indexed[2], 32) && std.assertEqual(made[99999], 100000) && std.assertEqual(chain[49999], 99999) && std.assertEqual(std.reverse(withIdx), [23, 12, 1]) && std.assertEqual(std.foldl(function(acc, x) acc + x, std.makeArray(1000, function(i) i), 0), 499500) && +std.assertEqual(fused, [1, 4, 9, 16, 25]) && +std.assertEqual(fused[2], 9) && +std.assertEqual(std.reverse(fused), [25, 16, 9, 4, 1]) && +std.assertEqual(std.foldl(function(a, b) a + b, fused, 0), 55) && true