יישומים#
הפרקים הקודמים עסקו בלוגיקה בוליאנית במופשט. זה עוסק במקום שבו היא מופיעה לרוב ב־Perl פועל: אריתמטיקה ביטית, ביטויים רגולריים, וניבי הקיצור התופסים את מקום התנאים המפורשים.
ביטי: לוגיקה על ביטים של מספרים שלמים#
האופרטורים הביטיים מחילים פעולות בוליאניות על כל זוג ביטים בשני מספרים שלמים במקביל. מספר שלם בן 32 ביטים הוא, מנקודת מבטם של האופרטורים, שלושים ושניים ערכי ביט־אחד מקבילים.
אופרטור | נקרא | כלל לכל ביט |
|---|---|---|
| AND ביטי | כל ביט פלט = |
| OR ביטי | כל ביט פלט = |
| XOR ביטי | כל ביט פלט = |
| NOT ביטי | כל ביט פלט = |
| היסט שמאלה | היסט ביטים שמאלה, מילוי באפסים מימין |
| היסט ימינה | היסט ביטים ימינה |
אותם אופרטורים בוליאניים — ∧, ∨, ⊕, ¬ — מופיעים כאן בצורה מספרית טהורה. זה אינו צירוף מקרים; זוהי ההגדרה.
קביעה, איפוס, החלפה ובדיקה של דגל#
ארבע פעולות הדגל הבסיסיות על ביט יחיד:
use constant FLAG_VERBOSE => 0x01;
use constant FLAG_DRY_RUN => 0x02;
use constant FLAG_RECURSE => 0x04;
use constant FLAG_FORCE => 0x08;
my $flags = 0;
$flags |= FLAG_VERBOSE; # SET -- OR with the bit
$flags |= FLAG_DRY_RUN; # SET another
$flags &= ~FLAG_DRY_RUN; # CLEAR -- AND with the inverted bit
$flags ^= FLAG_VERBOSE; # TOGGLE -- XOR with the bit
my $on = $flags & FLAG_RECURSE;# TEST -- AND, then test truthiness
כל אחת מאלה היא יישום של אופרטור בוליאני על ביט אחד. קביעה היא bit ∨ flag, איפוס הוא bit ∧ ¬flag, החלפה היא bit ⊕ flag, בדיקה היא bit ∧ flag.
החלפה ב־XOR: החלפה ללא משתנה עזר#
ל־XOR יש שתי תכונות המצטרפות לטריק בלתי־נשכח: a ⊕ a = 0, ו־a ⊕ b ⊕ b = a. החילו אותן ברצף ותוכלו להחליף שני מספרים שלמים ללא משתנה שלישי:
my ($a, $b) = (0xFEED, 0xBEEF);
$a ^= $b; # a := a ⊕ b
$b ^= $a; # b := b ⊕ (a ⊕ b) = a
$a ^= $b; # a := (a ⊕ b) ⊕ a = b
print "a=$a b=$b\n"; # a=48879 b=65261 (0xBEEF, 0xFEED)
הטריק אינו שימושי בפועל ב־Perl — ($a, $b) = ($b, $a) מהיר יותר, ברור יותר, ועובד על כל סקלר לרבות מחרוזות והפניות. אך הוא מופיע בשני מקומות שחשובים:
קוד משובץ ללא רגיסטר פנוי. מיקרו־בקר עם שלושה ערכים שצריך לשנות בשני רגיסטרים פונה לזה.
פולקלור של ראיונות. לדעת שהוא קיים ומדוע הוא עובד (XOR הוא ההופכי של עצמו) שווה את שלושים השניות שלוקחות לקרוא.
הסיבה שהוא עובד היא בדיוק הזהות הבוליאנית מפרק טבלת האמת: x ⊕ y ⊕ y = x. כל אחת משלוש השורות לעיל היא יישום אחד של זהות זו.
טריקי ביט נפוצים#
חופן תבניות שתראו בקוד רגיש לביצועים:
$x & ($x - 1) # $x with its lowest set bit cleared
($x & ($x - 1)) == 0 # true when $x is a power of two (and non-zero)
$x | -$x # signed: zero iff $x was zero, non-zero otherwise
($x >> 31) & 1 # the sign bit, on a 32-bit signed integer
1 << $n # the integer with only bit $n set
$x & (1 << $n) # is bit $n set in $x?
כל אחת מאלה היא תרגיל במעקב אחר מה שהביטים עושים — חשיבה בוליאנית טהורה המיושמת 32 (או 64) פעמים במקביל.
ביטויים רגולריים: לוגיקה על קבוצות של מחרוזות#
regex מתאים קבוצה של מחרוזות. הקבוצה הריקה, הקבוצה היחידנית {"foo"}, הקבוצה האינסופית ״כל דבר שמתחיל בספרה״ — כולן קבוצות, וה־regex מציין אחת מהן.
ברגע שאתם רואים regex כקבוצה, לפעולות הבוליאניות יש משמעות גיאומטרית:
פעולה בוליאנית | על קבוצות | בתחביר regex |
|---|---|---|
OR (∨) | איחוד | חלופה: |
AND (∧) | חיתוך | זוג lookahead: |
NOT (¬) | משלים | lookahead שלילי: |
AND-NOT (a ∧ ¬b) | הפרש |
|
שני פרטים ספציפיים ששווה להוציא.
חלופה היא OR על קבוצות#
$s =~ /yes|no|maybe/;
מתאים לאיחוד של שלוש קבוצות יחידניות. אין בזה יותר מכך; | בתחביר regex הוא בדיוק ∨ הבוליאני.
בתוך מחלקת תווים המשמעות זהה:
$s =~ /[abc]/; # union of {"a"}, {"b"}, {"c"}
$s =~ /[^abc]/; # complement: anything NOT in {"a","b","c"}
[^...] הוא תחביר regex עבור ¬ המוחל על קבוצת תווים.
lookarounds הם AND ו־NOT#
regex ללא lookaround צורך תווים תוך כדי התאמה; שני הקצוות של חלופה A|B אינם יכולים שניהם להתאים לאותו טווח (רק ענף אחד מנצח). כדי לבטא גם A וגם B באותו מיקום, אתם צריכים lookaround: היגד ברוחב אפס הדורש תכונה מבלי לצרוך.
# matches only if the rest of the string is BOTH digits AND ≤ 4 chars
$s =~ /^(?=\d+$)(?=.{1,4}$)/;
# \_____/\_______/
# A B intersection: A ∧ B
(?=...) הוא lookahead חיובי; (?!...) הוא lookahead שלילי (ה־NOT הבוליאני). שילובם נותן לכם את אלגברת הקבוצות המלאה על פרדיקטי regex:
# "starts with a digit but is not the literal '0'":
# (digit) ∧ ¬(literal "0")
$s =~ /^(?=\d)(?!0$)/;
מדוע המסגור עוזר#
ברגע שאתם קוראים regex בדרך זו, הקריאות של תבניות מסובכות משתפרת באופן דרמטי. regex עם שני lookaheads אינו ״שני regexים מודבקים יחד״ — הוא חיתוך של שתי קבוצות. lookahead שלילי ואחריו התאמה חיובית אינו ״קודם דחה, אחר כך התאם״ — הוא הפרש קבוצות. האלגברה הבוליאנית שאתם כבר מכירים היא האלגברה של regexים; רק הסימון משתנה.
זרימת בקרה: לוגיקת קיצור כביצוע מותנה#
הפרק על אופרטורים הציג את כלל החזרת האופרנד עבור &&, ||, ו־//. כלל זה, בתוספת קדימות, מייצר משפחה קטנה של ניבים המחליפים if/else מפורש עבור לוגיקה מותנית קצרה:
# defaulting
my $port = $cfg{port} // 8080;
# guard with side-effect
open my $fh, '<', $path or die "open $path: $!";
# lazy initialisation (set if currently false)
$cache{$key} ||= compute($key);
# lazy initialisation (set if currently undef — a real `0` is kept)
$cfg{retries} //= 3;
# guard chain: do step 2 only if step 1 succeeded
my $rc = step_one() && step_two();
# logical selection inside an expression
my $label = $count == 1 ? "1 item" : "$count items";
כל אחד מאלה הוא ביטוי בוליאני שנבחר עבור תופעות הלוואי שלו — הקיצור מחליט האם האופרנד הימני בכלל רץ. open ... or die עובד בדיוק משום ש־or אינו מחשב את הצד הימני שלו כאשר השמאלי הוא אמת.
שתי עצות.
השתמשו ב־// וב־//= כאשר 0 או "" הוא ערך תקף. שינוי יחיד זה מנע יותר באגים מכל ניב Perl מודרני אחר; || המשתמש בברירת מחדל על אפס שהוגדר הוא אחת הדרכים הקלאסיות לאבד נתונים.
הושיטו יד אל ?: כאשר אחרת הייתם בונים שלשת if/else/השמת־סקלר. השילוש שומר על הביטוי כביטוי ועל ההשמה בשורה אחת:
# verbose
my $kind;
if ($n == 1) { $kind = 'singular' }
else { $kind = 'plural' }
# idiomatic
my $kind = $n == 1 ? 'singular' : 'plural';
אל תקננו ?: יותר משתי רמות עומק. מעבר לשתיים, הצורה המשורשרת קשה יותר לקריאה מ־if/elsif/else. השילוש הוא לבחירה; החלטות עוקבות שייכות לבלוק.
מה כדאי לזכור מהפרק הזה#
&,|,^,~הביטיים הם האופרטורים הבוליאניים המיושמים ביט־אחר־ביט במקביל; קביעה/איפוס/החלפה/בדיקה של דגל הם ארבעת היישומים על ביט יחיד.regex מציין קבוצה של מחרוזות;
|הוא איחוד,[^...]הוא משלים,(?=)ו־(?!)יוצרים חיתוך והפרש.&&,||,//,?:נותנים לכם את רוב זרימת הבקרה המותנית מבלי לכתובif. השתמשו ב־//כאשר0אמיתי; השתמשו ב־?:עבור בחירה קצרה בתוך ביטוי.
ראו גם#
perlop — ביטי — מלווה מדריך העיון לחלק הביטי, עם סמנטיקת ההיסט המלאה וצורות מחרוזת־בתים
&. |. ^. ~.עם סיומת.perlop — קישור —
=~ו־!~, האופרטורים המחברים מחרוזות להתאמת regex.מדריך ביטויים רגולריים — מדריך העיון המלא של שפת regex.
perlop — לוגי — אופרטורי הקיצור שמאחורי ניבי זרימת הבקרה.