Introduction
kScript v3 adds JavaScript-style arrow lambdas and a family of reducer methods on arrays. Together they replace most explicit for loops with one-line transformations, and they are the recommended way to iterate: every reducer pass is automatically protected by the runtime's iteration watchdogs, and the analyzer type-checks the callback's element types.
Pine Script has no lambdas at all. A transformation that takes a Pine for loop and manual accumulator state is typically a single expression in kScript: that conciseness is measured, not aspirational (the engine's own test suite asserts a representative transformation at half the Pine line count).
Lambda syntax
var double = (x) => x * 2
var label = (p, side) => {
var prefix = side > 0 ? "bid" : "ask"
return format("{0} @ {1}", prefix, tostring(p))
}
var y = double(21) // 42Two forms:
- Expression body:
(x) => x * 2, the expression is the return value. - Block body:
(x) => { ... return v }, with explicitreturn.
Lambdas are values. Store them in var variables, pass them to functions and reducers, return them from functions, and call them directly. They close over the surrounding scope:
var threshold = high * 0.99
var nearHigh = prices.filter((p) => p > threshold)The reducer methods
All seven take a lambda and iterate a snapshot of the array (mutating the array inside the callback does not affect the active pass):
| Method | Callback | Returns |
|---|---|---|
map(fn) | (value, index?) => newValue | New array of transformed values |
filter(fn) | (value, index?) => boolean | New array of passing values |
reduce(fn, initial) | (accumulator, value, index?) => accumulator | The final accumulator |
forEach(fn) | (value, index?) => void | Nothing (side effects) |
find(fn) | (value, index?) => boolean | First match, or na |
some(fn) | (value, index?) => boolean | true if any match |
every(fn) | (value, index?) => boolean | true if all match |
var sizes = book.map((lvl) => lvl[1])
var bigLevels = book.filter((lvl) => lvl[1] > 50)
var totalSize = sizes.reduce((sum, s) => sum + s, 0)
var hasWall = book.some((lvl) => lvl[1] > 500)Callback arity is lenient
A callback may declare fewer or more parameters than the method supplies; parameters the method does not provide are bound to na. Direct lambda calls (not through a reducer) keep strict arity checking. This mirrors how every mainstream language treats callbacks and keeps one-parameter lambdas clean.
The microstructure idiom
Reducers are how kScript turns raw order-flow rows into indicator values. The canonical pattern, from cells to metric to plot:
timeseries fp = source("footprint", symbol=currentSymbol)
// net delta for the current bar
timeseries delta = fp.cells.map((c) => c[2] - c[3]).reduce((s, x) => s + x, 0)
// price of the largest-volume bucket
var top = fp.cells.reduce((best, c) =>
(c[2] + c[3]) > (best[2] + best[3]) ? c : best, fp.cells[0])
plotLine(top[0], color="#f59e0b")Every windowed indicator in the TA library also accepts microstructure-derived series, so rsi(source=delta, period=14) is a delta-RSI in one line. None of this is expressible in Pine Script: it has neither the data nor the lambdas.
Loops still exist, and they are guarded
for, for...in, for...of, and while remain available for logic that does not fit a reducer. Every loop body and every reducer pass is protected by the same watchdogs: a per-loop iteration ceiling, a per-run aggregate ceiling, and mid-bar interruption support, all reporting precise line/column errors. A runaway loop terminates loudly; it cannot hang a chart. The numbers live in Limitations.
Rule of thumb: reach for a reducer first, for...in/for...of second, and a manual indexed loop only when you genuinely need index arithmetic.