תיחום

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 - לקסיקלי רגיל; מאותחל מחדש בכל כניסה לבלוק, הניגוד הטבעי ל־state

  • our - כינוי לקסיקלי למשתנה חבילה; השתמשו כשקוראים זקוקים לראות או לעשות local לערך

  • local - תיחום דינמי למשתני חבילה; אורתוגונלי ל־state ולא ניתן להחלה עליו

  • sub - state חי בתוך CV; הבנת ההבדלים בין subs בעלי שם לאנונימיים מסבירה את ה־state לכל־closure

  • use - use v5.10 ומאוחר יותר מפעילים את תכונת ה־state כחלק מחבילת הגרסה

  • undef - placeholder חוקי בתוך הצהרות רשימה של state (...)