state#
מצהיר על משתנה בעל תיחום לקסיקלי שערכו נשמר בין קריאות לתת־השגרה הסובבת.
למשתנה state יש אותם כללי נראות כמו למשתנה my - הוא נראה רק בתוך הבלוק המצהיר עליו - אך הוא מאותחל בדיוק פעם אחת לכל closure סובב, לא בכל כניסה לבלוק. זוהי הדרך המובנית של Perl לכתוב משתנים פרטיים קבועים: מונים, מטמוני memoisation, טבלאות איתור שמחושבות פעם אחת, כל דבר שאחרת הייתם מסתירים בתוך לקסיקלי בתחום קובץ מעל ל־sub.
תקציר#
state $scalar
state $scalar = EXPR
state ($a, $b, undef, $c)
state @array
state %hash
state TYPE $var
state $var : ATTRS
מה מקבלים בחזרה#
state הוא הצהרה, לא פונקצייה. כמו my הוא מחזיר את המשתנה(ים) שעליו(הם) הוא מצהיר, אז ניתן להשתמש בו בכל מיקום ביטוי שבו המשתנה המוצהר יופיע:
(state $n //= 0)++; # declare-and-use in one expression
print ++(state $calls), "\n"; # count calls to this line
המאתחל מימין ל־= מוערך רק בפעם הראשונה שהבקרה זורמת דרך ההצהרה באותו מופע closure ספציפי. בכל כניסה נוספת, ההצהרה חושפת מחדש את המשתנה שכבר אותחל והמאתחל מדולג.
תחום הקביעות - מה ״פעם אחת״ אומר#
״פעם אחת לכל closure סובב״ הוא הכלל המדויק. משתנה state חי כל עוד ה־CV (code value) המכיל את ההצהרה שלו:
בsub בעל שם, ה־CV נוצר פעם אחת בזמן הידור. משתנה ה־
stateמאותחל בקריאה הראשונה ושומר על ערכו בכל קריאה מאוחרת למשך חיי התהליך.בsub אנונימי / closure, כל הערכה של
sub { ... }מפיקה CV חדש עם חריץstateחדש משלו. שני closures שנעשו מאותו מקור אינם חולקים מצב.בבלוק מקונן בתוך sub, ה־
stateעדיין שייך ל־CV הסובב - הוא אינו נוצר מחדש בכל כניסה לבלוק הפנימי.
sub counter { state $n = 0; ++$n }
counter(); counter(); counter(); # returns 1, 2, 3
my $make = sub { sub { state $n = 0; ++$n } };
my $a = $make->();
my $b = $make->();
$a->(); $a->(); $b->(); # $a sees 1, 2; $b sees 1
שער התכונה וזמינות#
state תמיד היה מאחורי שער־תכונה. שתי דרכים להפעילו:
use feature 'state';- מפעיל רק את התכונה הזו.use v5.10;(או כל חבילת גרסה מאוחרת יותר, כוללuse v5.36ו־use v5.42) - מובלע דרך חבילת תכונות הגרסה. לכל תוכנית מודרנית כבר ישstateזמין דרך שורת ה־use vX.Yשלה.
ללא התכונה בתחום, state מפוענח כ־bareword רגילה. כדי להגיע אל הפנימי ללא תנאי, כתבו CORE::state.
use v5.36; # 'state' feature is on by default
sub hits { state $n = 0; ++$n }
הצהרות רשימה ומאתחלים#
state תומך בצורת הרשימה בסוגריים להצהרה על מספר משתנים בבת אחת, עם undef כ־placeholder:
state ($x, $y, undef, $z);
מה שהוא אינו תומך בו עדיין הוא אתחול רשימה של משתני state. המקור מציין זאת במפורש: אתחול של משתני state ברשימה בסוגריים אינו אפשרי כעת, אז צורת הרשימה שימושית רק להצהרה.
אגרגטים - state @array ו־state %hash - היו היסטורית עם אותה הגבלה על מאתחלים (Perl הפיק אזהרת ניסיוניות, ובמספר גרסאות שגיאת תחביר, עבור state @a = (1, 2, 3)). בנו את האגרגט בתוך הגוף במקום:
sub primes_below_100 {
state @p;
unless (@p) {
@p = _sieve(100); # fill on first call only
}
return @p;
}
הניב unless (@p) / unless (%h) למעלה הוא התחליף הנייד למאתחל אגרגט.
מיקום הצהרה וסמנטיקת אותה־הצהרה#
כמו my, our, ו־local, הצהרת state יכולה להופיע בכל מקום שבו מותר ביטוי (למעט בתוך שילוב מחרוזות). הקישור החדש נכנס לתוקף להצהרות שלאחר מכן, לא לאזכורים אחרים של אותו משתנה באותה הצהרה:
package main;
use feature 'state';
our $x = 2;
foo($x, state $x = $x + 1, $x); # foo() receives (2, 3, 2)
foo($x, $main::x); # foo() receives (3, 2)
ה־$x הראשון וה־$x הנגרר באותה קריאה עדיין מתייחסים ל־our $x; רק ההצהרה הבאה רואה את ה־state $x.
דוגמאות#
מונה פגיעות - ה־one-liner הקנוני:
sub hits { state $n = 0; ++$n }
hits(); hits(); hits(); # returns 1, 2, 3
Memoisation של פונקצייה טהורה יקרה:
sub fib {
my ($n) = @_;
state %cache;
return $cache{$n} //= $n < 2 ? $n : fib($n-1) + fib($n-2);
}
התקנה עצלה חד־פעמית של טבלה לקריאה־בלבד:
sub months {
state @m = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
return @m;
}
כאן המאתחל הוא רשימה פשוטה, מה שעובד כי האגף הימני של state @m = ... מקופל־כקבוע לליטרל רשימה ו־Perl הנוכחי מתיר את הצורה הזו; המקרה הכללי יותר של רשימה מחושבת עדיין רוצה את הדפוס unless (@m) { ... } למעלה לניידות.
מצב לכל closure (חריץ חדש לכל הערכת sub { ... }):
sub make_counter {
return sub { state $n = 0; ++$n };
}
my $a = make_counter();
my $b = make_counter();
$a->(); $a->(); # 1, 2
$b->(); # 1 - independent of $a
הצהרה־ושימוש בביטוי בודד:
while (my $line = <$fh>) {
warn "first line was: $line" if (state $first //= $line) eq $line;
}
מקרי קצה#
מאתחלי אגרגט. ל־
state @a = (1, 2, 3)ו־state %h = (k => 1)יש היסטוריה ארוכה של הגבלות או של היותם ניסיוניים. בספק, הצהירו על האגרגט ומלאו אותו תחתunless (@a) { ... }בקריאה הראשונה. מאתחלים סקלריים (state $x = EXPR) תמיד היו בסדר.שימוש חוזר באותה הצהרה. הצהרת
stateחדשה אינה חלה על אזכורים אחרים של אותו שם בתוך אותה הצהרה; אלה עדיין נפתרים למה שהיה בתחום לפני ההצהרה. ראו את הדוגמהfoo($x, state $x = $x + 1, $x)למעלה.אזהרת הצללה. הצהרה מחדש של אותו שם באותו תחום -
state $n; state $n;- מצלה על הראשון. תחתuse warningsזה מפיק אזהרה בקטגורייתshadow. כמעט תמיד באג.ריבוי של sub אנונימי. כל הערכה של
sub { ... }מפיקה CV חדש עם חריץstateמשלו. אם בונים closures רבים מאותו מקור, לכל אחד ישstateעצמאי משלו - זוהי תכונה, אך היא מפתיעה אנשים שמצפים לקביעות בתחום קובץ.Threads ו־forks.
stateחי ב־CV, שמשוכפל ב־fork(copy-on-write במובן ה־OS - כל תהליך ממשיך עם עותק משלו) וב־threads->create(משוכפל בעומק). שני תהליכים או threads אינם חולקים מונהstate.localאינו חל. משתניstateהם לקסיקליים, לא משתני חבילה, אזlocalאינו יכול לשמור ולשחזר אותם. עטפו את ה־sub כולה בתחום משלה או השתמשו במנגנון שונה אם אתם זקוקים לתיחום דינמי.גישה דרך
CORE::state. בעת כתיבת קוד שחייב להתפענח אפילו ללא התכונה מופעלת, הוסיפו תחילית למילת המפתח:CORE::state $n = 0;.הריסה. משתני
stateחיים כל עוד ה־CV. עבור sub בעל שם בתוכנית הראשית זה אומר ״עד להריסה גלובלית.״ אל תשימו מטפל קובץ או buffer גדול במשתנהstateבציפייה שישוחרר ביציאת ה־sub - הוא לא ישוחרר.
הבדלים מהמקור#
תאימות מלאה עם Perl 5.42 המקורי.
ראו גם#
my- לקסיקלי רגיל; מאותחל מחדש בכל כניסה לבלוק, הניגוד הטבעי ל־stateour- כינוי לקסיקלי למשתנה חבילה; השתמשו כשקוראים זקוקים לראות או לעשותlocalלערךlocal- תיחום דינמי למשתני חבילה; אורתוגונלי ל־stateולא ניתן להחלה עליוsub-stateחי בתוך CV; הבנת ההבדלים בין subs בעלי שם לאנונימיים מסבירה את ה־state לכל־closureuse-use v5.10ומאוחר יותר מפעילים את תכונת ה־stateכחלק מחבילת הגרסהundef- placeholder חוקי בתוך הצהרות רשימה שלstate (...)