---
title: "Lambdas and reducers"
description: "Arrow functions, function values, and the loops that do the work of the reducer methods (map, filter, reduce, forEach, some, every). A wrun indicator has arrow…"
order: 21
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/lambdas-and-reducers.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Lambdas and reducers

Arrow functions, function values, and the loops that do the work of the
reducer methods (`map`, `filter`, `reduce`, `forEach`, `some`, `every`).
A wrun indicator has arrow functions, with one rule that changes how you
use them: a function cannot capture a local variable, so the "closure" is
module-level state and a reducer is a plain loop over a buffer.

## Lambda syntax

An arrow function has typed parameters and a typed return, in either the
expression or the block form. It is a value: store it in a variable typed
`(x: f64) => f64`, pass it to a function, call it.

```text
const double = (x: f64): f64 => x * 2.0;
const label = (side: f64): string => {
  return side > 0.0 ? "bid" : "ask";
};

let pick: (x: f64) => bool = above;   // a named function is a value too
const y = double(21.0);               // 42
```

Both forms need every type written out: an untyped parameter or a missing
return type is a parse error (`Type expected.`).

## The one rule: no captured locals

The JavaScript habit is a lambda that closes over the surrounding scope:
`const threshold = high * 0.99; prices.filter((p) => p > threshold)`. In
an indicator that exact shape, an arrow function inside `onBar()` reading
a `const` of `onBar()`, is refused at compile time:

```text
ERROR AS100: Not implemented: Closures
```

The compiler cannot build a function that carries a copy of another
function's locals. What a function **can** read is module-level state,
which is where an indicator keeps everything that matters anyway. So the
pattern is: put the threshold in a module-level `let`, write the predicate
as a named function (or an arrow at module scope), and pass it.

```typescript sample=cc-lambdas-pointer
param("bars", 20, { min: 1, max: 200, description: "Bars in the window" });
output("above_count", line, lower, { color: "#16a34a", description: "Bars in the window closing above the window mean" });
output("near_high_count", line, lower, { color: "#f59e0b", description: "Bars in the window within 1% of the window high" });

const MAX_BARS = 200;
const closes = new StaticArray<f64>(MAX_BARS);
let n: i32 = 20;
let cursor: i32 = 0;
let count: i32 = 0;
let mean: f64 = NaN; // module-level: the "closure" every predicate reads
let nearHighLevel: f64 = NaN;

// Predicates are named functions over module-level state, never over a local.
function aboveMean(x: f64): bool {
  return x > mean;
}

function nearHigh(x: f64): bool {
  return x >= nearHighLevel;
}

// A reducer is a loop that takes the predicate as a value.
function countWhere(xs: StaticArray<f64>, len: i32, pred: (x: f64) => bool): i32 {
  let hits = 0;
  for (let i = 0; i < len; i++) if (pred(xs[i])) hits += 1;
  return hits;
}

function windowMax(xs: StaticArray<f64>, len: i32): f64 {
  let m = -Infinity;
  for (let i = 0; i < len; i++) if (xs[i] > m) m = xs[i];
  return m;
}

function windowMean(xs: StaticArray<f64>, len: i32): f64 {
  let s = 0.0;
  for (let i = 0; i < len; i++) s += xs[i];
  return s / f64(len);
}

function onStart(): void {
  n = i32(p_bars());
}

function onBar(): void {
  closes[cursor] = bar.close();
  cursor = (cursor + 1) % n;
  if (count < n) count += 1;
  if (count < n) return;
  mean = windowMean(closes, n);
  nearHighLevel = windowMax(closes, n) * 0.99;
  out_above_count(f64(countWhere(closes, n, aboveMean)));
  out_near_high_count(f64(countWhere(closes, n, nearHigh)));
}
```

`countWhere` is the reducer; `aboveMean` and `nearHigh` are the lambdas,
reading `mean` and `nearHighLevel` from module scope instead of capturing
them. The same function value can be passed to any loop that takes a
`(x: f64) => bool`.

## The reducer methods

`Array<f64>` has the familiar methods, and each callback is a non-capturing
function with typed parameters (trailing parameters may be omitted):

| Method | Callback | Returns | Allocates? |
| --- | --- | --- | --- |
| `map<U>(fn)` | `(value: f64, index?: i32) => U` | a new array | yes |
| `filter(fn)` | `(value: f64, index?: i32) => bool` | a new array | yes |
| `reduce<U>(fn, initial)` | `(acc: U, value: f64, index?: i32) => U` | the final accumulator | no |
| `forEach(fn)` | `(value: f64, index?: i32) => void` | nothing | no |
| `findIndex(fn)` | `(value: f64, index?: i32) => bool` | the first matching index, or `-1` | no |
| `some(fn)` | `(value: f64, index?: i32) => bool` | `true` if any match | no |
| `every(fn)` | `(value: f64, index?: i32) => bool` | `true` if all match | no |

There is no `find`: use `findIndex` and read the element. `map` and
`filter` return a **new** array every call, and the module never frees
memory, so they belong in `onStart()` (building a lookup table once) and not
in `onBar()`. On the per-bar path, write the loop over a `StaticArray` you
allocated once; the window example above is the template, and
`collections.md` has every numeric reducer as a method.

```text
// onStart(): fine, once
const doubled = periods.map<f64>((p: f64): f64 => p * 2.0);

// onBar(): a loop over a preallocated buffer, no allocation
let sum = 0.0;
for (let i = 0; i < n; i++) sum += window[i];
```

## The microstructure idiom

Where you would reach for a `map` and a `reduce` over order-flow rows
(buy minus sell per bucket, summed), an indicator reads the bar's cells
(one `[low, high, buy, sell]` row per price bucket) through the input's
view and loops over them: the net delta of the bar, and the price of its
largest-volume bucket (the point of control).

```typescript sample=cc-lambdas-delta
input("close", ohlcv.close);
input("profile", volume_profile.cells, { max_cells: 8192 });
output("delta", line, lower, { color: "#2563eb", description: "Net per-bar delta: buy minus sell volume across every price bucket" });
output("poc", line, overlay, { color: "#f59e0b", description: "Price of the bucket that traded the most volume" });

function onBar(): void {
  const n = in_profile_cells();
  if (n <= 0) return; // no block this bar
  const cells = in_profile_view(); // max_cells tuples of 4 f64; only the first n values are this bar's
  let net = 0.0;
  let best = -1.0;
  let bestPrice = NaN;
  for (let i = 0; i + 3 < n; i += 4) {
    const buy = cells[i + 2];
    const sell = cells[i + 3];
    net += buy - sell; // the map + reduce, as one pass
    if (buy + sell > best) {
      best = buy + sell;
      bestPrice = (cells[i] + cells[i + 1]) / 2.0; // the bucket's midpoint
    }
  }
  out_delta(net);
  out_poc(bestPrice);
}
```

Every windowed class in the TA kit accepts any number, so `rsi.update(delta)`
is a delta-RSI in one line. The celled input, its accessors, and the
`max_cells` contract are in `data-sources.md`.

## Loops still exist, and you bound them

`for`, `while`, and `do` are the iteration tools (`for...of` is not
implemented: `Not implemented: Iterators`, so index loops it is). There is
no per-loop ceiling inside the module; size every loop by a param with a
declared `max`. A loop that runs away is stopped by the host's execution
timeout and the evaluation is refused: it cannot hang the chart, and it
cannot produce a value either.
