millionchart
فارسی

Lazy operators, E3302 and W3302

The right side of and, or and ?? and the arms of if-then-else run only when needed. What that protects, and why a call that keeps a state may not stand there.

In edition 1 four forms look at their second part only when the first part has not already decided the answer:

FormThe second part runs
a and bonly when a is true
a or bonly when a is false
a ?? bonly when a is na
if c then x else yonly the arm that c chooses

Nothing in the part that does not run is read, calculated or counted against the budget. This is the usual meaning of these words in most programming languages, and the language writes it down because it decides two things: what you can protect with a guard, and where a call that keeps a state may stand.

A guard keeps a read from failing#

Reading an item that is not in an array stops the run with E5602. A test on the left of and keeps the read on the right from happening when the item does not exist:

Cuprel
// The right side of "and" runs only when the left side is true, and so does an arm of "if ... then ... else" that is not chosen: neither is read at all.
// That lets a guard protect a read that would fail. The array holds the rises of the close; its tenth item does not exist until ten rises have been seen,
// and the size test on the left keeps the read on the right from ever failing.
indicator "Guarded read"
    edition = 1
    display = pane

state gains: array<float> = array<float>()
if close > close[1]
    gains.push(close - close[1])
let tenth_is_big = gains.size() > 9 and gains.at(9) > 2
let newest = if gains.size() > 0 then gains.last() else 0.0
let flag = if tenth_is_big then 1 else 0
plot(flag, name = "Tenth rise is big", style = histogram, color = color.green)
plot(newest, name = "Newest rise", color = color.blue)
plot(gains.size(), name = "Rises seen", display = none)

Without the size test the same line fails on the first bar, because the array is empty when the read happens:

Cuprel · error E5602
// A mistake on purpose: without the size test on the left, the tenth item is read before it exists.
indicator "Guarded read"
    edition = 1
    display = pane

state gains: array<float> = array<float>()
if close > close[1]
    gains.push(close - close[1])
let tenth_is_big = gains.at(9) > 2
let newest = if gains.size() > 0 then gains.last() else 0.0
let flag = if tenth_is_big then 1 else 0
plot(flag, name = "Tenth rise is big", style = histogram, color = color.green)
plot(newest, name = "Newest rise", color = color.blue)
plot(gains.size(), name = "Rises seen", display = none)

?? and the arms of if ... then ... else guard in the same way: known ?? items.at(99) never reads the array while known has a value, and if ready then items.at(0) else 0.0 reads the array only when ready is true.

What may stand in the second part#

Anything that only calculates from values the script already has is fine: arithmetic, comparisons, names bound earlier, close[1], the methods of collections, functions of yours that keep no state, the members of a session. This program mixes them all and the checker says nothing:

Cuprel
// `and`, `or`, `??` and the two arms of `if ... then ... else` run their right side only when it is needed.
// Pure operands (plain arithmetic, comparisons, values already bound to a name) are always fine there.
indicator "Lazy operators"
    edition = 1
    display = price

let average = tech.sma(close, 20)
let above = close > average
let near = math.abs(close - average) < tech.atr(14)
let signal = above and near
let fallback = average ?? close
plot(if signal then fallback else na, name = "Near and above", style = circles)

One kind of call does not belong there: a call that keeps a state (Calls that keep a state). Its state is tied to the place where it is written and moves when the script reaches the place. In the second part of one of these forms the script reaches the place on some bars only, and the line does not say which. The language refuses it:

Cuprel · error E3302
// Mistake: a call that keeps a state (tech.sma remembers its window) stands in the right side of `and`.
// That side runs only when the left side is true, so the window would move on some bars only. The checker refuses it (E3302).
indicator "Stateful call in an operand"
    edition = 1
    display = price

let rising = close > open
let strong = rising and close > tech.sma(close, 20)
plot(if strong then close else na, name = "Strong bars", style = circles)

The same happens in the arm of a one-line if:

Cuprel · error E3302
indicator "Stateful call in an arm"
    edition = 1
    display = price

let fast = if close > open then tech.ema(close, 5) else na
plot(fast)

What counts as a call that keeps a state is the list on Calls that keep a state: the functions of tech, rises and falls, tf.changed, math.random, data.series, the history of an expression such as (high - low)[1], and a function of yours that holds a state or calls one of these. tech.sma(close, 20), math.random(), tf.changed("D") and rises(close > open) in an operand are all E3302. close[1], london.high and prior(day).high are not.

Two ways to mend it#

Run the call on every bar. Write it on a line of its own, above, and use the name. The call is reached on every bar, so its state never skips one:

Cuprel
// The fix for E3302: bind the call to a name on its own line. It then runs on every bar and the window never skips one.
indicator "Stateful call bound first"
    edition = 1
    display = price

let average = tech.sma(close, 20)
let rising = close > open
let strong = rising and close > average
plot(if strong then close else na, name = "Strong bars", style = circles)

Run the call only where it is wanted. Write the condition as a block if. The call is then reached on the bars where the block runs, and the state waits on the others. This is legal, and the warning W3302 says so, because it is seldom what the line seems to say:

Cuprel · warning W3302
// Warning W3302: a call that keeps a state stands in an `if` block. It is allowed, but its state moves only on the bars where the block runs.
// Here the average of the up bars is smoothed over the up bars only, which is rarely what the line says.
indicator "Stateful call in a block"
    edition = 1
    display = price

mut smoothed: float = na
if close > open
    smoothed = tech.ema(close, 5)
plot(smoothed, name = "Smoothed")
Cuprel
// The fix for W3302: run the call on every bar above the block, and let the block only choose what to keep.
indicator "Stateful call above the block"
    edition = 1
    display = price

let smoothed_all = tech.ema(close, 5)
mut smoothed: float = na
if close > open
    smoothed = smoothed_all
plot(smoothed, name = "Smoothed")

A loop is a third case and has no way out: a call that keeps a state may not stand in a for or while at all (E3301).

Precedence#

?? binds tighter than a comparison and looser than +, and it groups to the right: x ?? 0 > 5 means (x ?? 0) > 5, and a ?? b ?? c means a ?? (b ?? c). not binds looser than a comparison, so not a == b means not (a == b). A comparison is not chained: a < b < c is an error (E2010).

See also: Choices and loops, Diagnostics, Examples: lazy operators.