عملگرهای تنبل، E3302 و W3302
سمت راست and، or و ?? و بازوهای if-then-else فقط وقتی لازم باشند اجرا میشوند. این چه چیزی را محافظت میکند و چرا فراخوانیِ حالتدار نباید آنجا باشد.
در edition 1 چهار شکل، بخش دومشان را فقط وقتی نگاه میکنند که بخش اول هنوز جواب را مشخص نکرده باشد:
| شکل | بخش دوم اجرا میشود |
|---|---|
a and b | فقط وقتی a درست باشد |
a or b | فقط وقتی a نادرست باشد |
a ?? b | فقط وقتی a برابر na باشد |
if c then x else y | فقط بازویی که c انتخاب میکند |
از بخشی که اجرا نمیشود هیچ چیز خوانده، حساب یا از بودجهٔ اجرا کم نمیشود. این همان معنای معمول این واژهها در بیشتر زبانهای برنامهنویسی است، و زبان آن را صریح مینویسد چون دو چیز را تعیین میکند: اینکه با یک شرط محافظ (guard) چه چیزی را میشود محافظت کرد، و اینکه فراخوانیِ حالتدار کجا میتواند بنشیند.
شرط محافظ، خواندن را از شکست نگه میدارد#
خواندن عضوی که در آرایه نیست، اجرا را با E5602 متوقف میکند. آزمونی در سمت چپ and نمیگذارد خواندن سمت راست انجام شود وقتی آن عضو وجود ندارد:
// 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)بدون آزمون اندازه، همان خط روی اولین کندل شکست میخورد، چون وقتی خواندن انجام میشود آرایه خالی است:
// 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)?? و بازوهای if ... then ... else هم به همین شکل محافظت میکنند: known ?? items.at(99) تا وقتی known مقدار دارد آرایه را نمیخواند، و if ready then items.at(0) else 0.0 فقط وقتی ready درست باشد آرایه را میخواند.
در بخش دوم چه چیزی میتواند باشد#
هر چیزی که فقط از مقدارهایی حساب میکند که اسکریپت از قبل دارد، مشکلی ندارد: حساب، مقایسه، نامهایی که قبلاً مقدار گرفتهاند، close[1]، متدهای مجموعهها، تابعهای خودتان که حالتی نگه نمیدارند، و عضوهای یک سشن. این برنامه همهٔ آنها را با هم دارد و بررسیکننده هیچ چیزی نمیگوید:
// `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)یک نوع فراخوانی آنجا جایی ندارد: فراخوانیای که حالت نگه میدارد (فراخوانیهایی که حالت نگه میدارند). حالت آن به جایی بسته است که نوشته شده و وقتی اسکریپت به آن جا میرسد جلو میرود. در بخش دوم یکی از این شکلها، اسکریپت فقط روی بعضی کندلها به آن جا میرسد و خط نمیگوید کدام کندلها. زبان آن را نمیپذیرد:
// 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)همین اتفاق در بازوی یک if یکخطی هم میافتد:
indicator "Stateful call in an arm"
edition = 1
display = price
let fast = if close > open then tech.ema(close, 5) else na
plot(fast)فهرست فراخوانیهای حالتدار در صفحهٔ فراخوانیهایی که حالت نگه میدارند است: تابعهای tech، rises و falls، tf.changed، math.random، data.series، تاریخچهٔ یک عبارت مثل (high - low)[1]، و تابعی از خودتان که یک state دارد یا یکی از اینها را صدا میزند. tech.sma(close, 20)، math.random()، tf.changed("D") و rises(close > open) در یک عملوند همه E3302 هستند. close[1]، london.high و prior(day).high نیستند.
دو راه اصلاح#
فراخوانی را روی هر کندل اجرا کنید. آن را در یک خط جدا، بالاتر، بنویسید و از نامش استفاده کنید. فراخوانی روی هر کندل اجرا میشود، پس حالتش هیچ کندلی را جا نمیاندازد:
// 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)فراخوانی را فقط جایی اجرا کنید که میخواهید. شرط را به شکل یک if بلوکی بنویسید. آن وقت فراخوانی روی کندلهایی اجرا میشود که بلوک اجرا میشود و حالت روی بقیه منتظر میماند. این مجاز است و اخطار 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")// 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")حلقه حالت سوم است و راه فراری ندارد: فراخوانیِ حالتدار اصلاً نمیتواند در for یا while بنشیند (E3301).
اولویت#
?? از مقایسه محکمتر و از + سستتر میچسبد و از راست گروهبندی میشود: x ?? 0 > 5 یعنی (x ?? 0) > 5، و a ?? b ?? c یعنی a ?? (b ?? c). not از مقایسه سستتر میچسبد، پس not a == b یعنی not (a == b). مقایسه زنجیره نمیشود: a < b < c خطاست (E2010).
ببینید هم: انتخابها و حلقهها، تشخیصها، مثالها: عملگرهای تنبل.