# מ־PHP ל־Perl אתם מכירים את PHP. אתם פונים אל `$arr['key']` בלי לחשוב, `foreach ($items as $item)` הוא זיכרון שרירי, ואתם מצרפים מחרוזות עם `.` כי ברור שכך. Perl ו־PHP הן בנות־דודות קרובות: שתיהן שפות המשתמשות ב־sigils, פרגמטיות, ושעושות את העבודה, ו־PHP שאלה רבות מ־Perl בימיה הראשונים. המדריך הזה ממפה את הרגלי ה־PHP שלכם אל Perl כך שתוכלו להיות יצרניים ב־`pperl` תוך אחר צהריים אחד. שני זעזועים נוחתים בחמש הדקות הראשונות, אז הכירו אותם עכשיו: - **ה־sigil ‏`$` אינו כל הסיפור.** ב־PHP כל משתנה הוא `$x`, תמיד. ב־Perl ה־sigil משתנה לפי האופן שבו אתם *ניגשים* אל הערך: `@a` הוא המערך כולו אבל `$a[0]` הוא איבר אחד; `%h` הוא ה־hash כולו אבל `$h{k}` הוא ערך אחד. הדולר שאתם כבר מכירים מסמן סקלר יחיד; `@` ו־`%` הם חדשים, וחלק ראשון אינו עוסק בשום דבר אחר. - **ה־`array` היחיד של PHP הופך לשני הטיפוסים של Perl.** `array` ב־PHP הוא מיפוי מסודר שמשמש גם כרשימה וגם כמילון. Perl מפצלת זאת ל־`@array` (ממופתח לפי מספרים) ו־`%hash` (ממופתח לפי מחרוזות), ואתם בוחרים מראש איזה מהם יש לכם. הפיצול הזה מניע את חלקי המערכים וה־hashים. Every example here runs on `pperl`, a Rust reimplementation of Perl 5.42. Each Perl snippet was executed on `pperl`; the `# ...` comments show its real output. Start your scripts with: ```perl use v5.42; ``` השורה האחת הזו מפעילה את `strict` (ללא משתנים גלובליים בשוגג), את `warnings`, את הפונקציה המובנית `say` (זהו `print` עם תו שורה חדשה בסופו, כמו `echo` של PHP בתוספת `PHP_EOL`), חתימות של תת־שגרות, ואת מערכת התכונות המודרנית המשמשת לאורך כל המדריך הזה. ## כיצד לקרוא את זה - [משתנים ו־sigils]() - המעבר בין `$`/`@`/`%`. - [מחרוזות]() - שִחלוף ושרשור עם `.`. - [מספרים]() - `==` מול `eq`, מלכודת ה־PHP הקלאסית. - [מערכים ורשימות]() - חצי מה־`array` שלכם. - [Hashים]() - החצי השני של ה־`array` שלכם. - [הקשר]() - המושג שאין ל־PHP מילה עבורו. קראו את זה. - [אמיתות]() - מה נחשב לשקר, עם הפתעות. - [בקרת זרימה]() - `if`, לולאות, הצורות הסופיתיות. - [תת־שגרות]() - `function` הופך ל־`sub`. - [הפניות]() - נתונים מקוננים ו־closures. - [ביטויים רגולריים]() - regex כתחביר, לא `preg_*`. - [קלט/פלט של קבצים וזרמים]() - `fopen`, קריאות שורה, slurp. - [מודולים וחבילות]() - `require`/`use` הופך ל־`use`. - [אובייקטים]() - תחביר ה־`class` המודרני. - [טיפול בשגיאות]() - `try`/`catch`, אותו רעיון. - [ניבים]() - שורות־היחיד היומיומיות. - [מלכודות בהגעה מ־PHP]() - סרקו לפני שאתם כותבים. ## משתנים ו־sigils אתם כבר משתמשים ב־`$`, ולכן החצי הראשון של ה־sigils של Perl ניתן לכם בחינם. הרעיון החדש הוא ש**ה־sigil מסמן את הגישה, לא את המשתנה**. המערך כולו הוא `@a`; איבר אחד הוא `$a[0]`. ה־hash כולו הוא `%h`; ערך אחד הוא `$h{k}`. PHP שומרת על `$` על הכול ומשנה את הסוגריים (`$a[0]`, `$h['k']`); Perl משנה את ה־sigil במקום זאת. `my` מצהיר על משתנה בעל היקף לקסיקלי, המקבילה לקישור מקומי רגיל. ```php $name = "Ada"; $langs = ["PHP", "Perl"]; $ages = ["Ada" => 36, "Rasmus" => 56]; ``` ```perl use v5.42; my $name = "Ada"; my @langs = ("PHP", "Perl"); my %ages = (Ada => 36, Rasmus => 56); say $name; # Ada say $langs[0]; # PHP say $ages{Ada}; # 36 say scalar @langs; # 2 ``` המלכודת: כשאתם נשלחים *לתוך* צבר כדי לקבל איבר אחד, ה־sigil עוקב אחר הערך שאתם מקבלים בחזרה, לא אחר המכל. איבר יחיד של `@langs` הוא סקלר, ולכן הוא `$langs[0]`, לא `@langs[0]`; ערך אחד מתוך `%ages` הוא `$ages{Ada}`. קראו את ה־sigil המוביל כ“איזו צורה אני מקבל“ - `$` עבור דבר אחד, `@` עבור רשימה, `%` עבור המיפוי כולו - ואת הסוגריים כ“מאיפה“. ## מחרוזות שרשור הוא `.`, בדיוק כמו ב־PHP. מירכאות כפולות משחלפות משתנים, מירכאות בודדות אינן עושות זאת - אותו פיצול ש־PHP עושה בין `"..."` ל־`'...'`. אין תחביר של `sprintf`־אל־תוך־המחרוזת ללמוד, מפני שמחרוזת ברירת המחדל במירכאות כפולות כבר משחלפת. ```php $name = "Ada"; $greeting = "Hello, $name"; $literal = 'Hello, $name'; $joined = "AB" . "CD"; $shout = str_repeat("AB", 3); ``` ```perl use v5.42; my $name = "Ada"; my $greeting = "Hello, $name"; # Hello, Ada my $literal = 'Hello, $name'; # Hello, $name (no interpolation) my $joined = "AB" . "CD"; # ABCD my $shout = "AB" x 3; # ABABAB ``` המלכודת: שכפול מחרוזת הוא האופרטור `x`, לא פונקציה - `"AB" x 3`, לא `str_repeat`. שִחלוף איבר מערך או hash אינו זקוק לבריחה בסוגריים מסולסלים: `"First: $langs[0]"` ו־`"Name: $h{name}"` שניהם עובדים כפי שנכתבו, במקום שבו PHP הייתה מאלצת אתכם לכתוב `"{$arr['k']}"`. כדי לשחלף את תוצאת *ביטוי*, עטפו אותו ב־`@{[ ... ]}`: `"sum: @{[ 2 + 3 ]}"` מדפיס `sum: 5`. ## מספרים ל־Perl יש טיפוס מספרי אחד ברמת השטח; אינכם מצהירים על `int` מול `float`. הדבר שבאמת ינשך אתכם הוא האופרטורים, אז קראו את המלכודת פעמיים. ```php echo 7 / 2; // 3.5 echo intdiv(7, 2); // 3 echo 2 ** 10; // 1024 echo 10 % 3; // 1 ``` ```perl use v5.42; say 7 / 2; # 3.5 say int(7 / 2); # 3 say 2 ** 10; # 1024 say 10 % 3; # 1 ``` המלכודת: ל־Perl יש **שתי משפחות אופרטורים ואתם בוחרים לפי טיפוס האופרנד, לא לפי המשתנה**. מספרים משתמשים ב־`==`, `!=`, `<`, `>`, `<=>`; מחרוזות משתמשות ב־`eq`, `ne`, `lt`, `gt`, `cmp`. ו־`+` הוא *תמיד* מספרי - אין העמסת אופרטורים כפי שה־`+` של PHP עושה איחוד מערכים. שרשרו עם `.`, חברו עם `+`, ולעולם אל תחצו ביניהם: ```perl use v5.42; say "12" . "3"; # 123 (string concat) say "12" + "3"; # 15 (numeric add) say "10" == "10.0" ? "same" : "diff"; # same (numeric compare) say "10" eq "10.0" ? "same" : "diff"; # diff (string compare) ``` PHP פרשה מבלגן ה־`==` הרופף בגרסאות מודרניות; ל־Perl מעולם לא היה כזה, אבל היא פותרת את אותה בעיה בכך שהיא מאלצת אתכם לומר באיזו השוואה אתם מתכוונים. `"10" == "10.0"` הוא אמת (מספרי), `"10" eq "10.0"` הוא שקר (מחרוזת). שימוש ב־`==` על מחרוזות, או ב־`eq` על מספרים, הוא הבאג הנפוץ ביותר במעבר מ־PHP ל־Perl. ## מערכים ורשימות כאן ה־`array` היחיד של PHP מתפצל לשניים. כשהשתמשתם ב־`array` כרשימה ממופתחת מספרית - `$items[0]`, `array_push`, `foreach` על ערכים - זהו **מערך** של Perl, המוצהר עם `@`. הפעלים משונים בשמם אבל הפעולות זהות, ואינדקסים שליליים עובדים כפי שאתם מצפים. ```php $nums = [1, 2, 3, 4, 5]; $nums[] = 6; $last = array_pop($nums); echo count($nums); // 5 print_r(array_slice($nums, 1, 2)); // [2, 3] ``` ```perl use v5.42; my @nums = (1, 2, 3, 4, 5); push @nums, 6; my $last = pop @nums; # 6 say scalar @nums; # 5 say "@nums[1,2]"; # 2 3 (a slice) ``` ה־`array_map` וה־`array_filter` שלכם הופכים ל־`map` ו־`grep`. `map` ממיר (`array_map` של PHP); `grep` מסנן (`array_filter` של PHP). `$_` הוא האיבר הנוכחי, משתנה הלולאה המובלע של Perl, כפי שפרמטר ה־callback הוא ב־PHP - אלא שאינכם נותנים לו שם. ```php $squares = array_map(fn($x) => $x * $x, $nums); $evens = array_filter($nums, fn($x) => $x % 2 == 0); rsort($nums); // descending, in place ``` ```perl use v5.42; my @nums = (1, 2, 3, 4, 5); my @squares = map { $_ * $_ } @nums; # 1 4 9 16 25 my @evens = grep { $_ % 2 == 0 } @nums; # 2 4 my @desc = sort { $b <=> $a } @nums; # 5 4 3 2 1 ``` המלכודת: `sort` משווה כמחרוזות כברירת מחדל, כך ש־`sort (10, 9, 100)` נותן `10 100 9`. עבור מספרים עליכם להעביר משווה: `sort { $a <=> $b } @nums`. `$a` ו־`$b` הם שני האיברים המושווים, ו־`<=>` הוא אופרטור התלת־כיווני המספרי (`cmp` הוא אחיו של המחרוזות). שימו לב גם ש־`grep` שומר על איברים ומחזיר רשימה חדשה, במקום שבו ה־`array_filter` של PHP משמר מפתחות - למערכי Perl אין מפתחות לשמר, ולכן התוצאה פשוט ממופתחת מחדש. ## האשים החצי השני של ה־`array` של PHP - החלק שהשתמשתם בו עם מפתחות מחרוזת, `$config['host']`, `array_key_exists`, `foreach ($h as $k => $v)` - הוא **hash** של Perl, המוצהר עם `%` וניגשים אליו ערך אחד בכל פעם עם `$h{key}`. ```php $h = ["apple" => 3, "pear" => 5, "plum" => 2]; echo $h["apple"]; // 3 $keys = array_keys($h); sort($keys); echo array_key_exists("pear", $h) ? "yes" : "no"; // yes unset($h["plum"]); foreach ($h as $k => $v) { echo "$k => $v\n"; } ``` ```perl use v5.42; my %h = (apple => 3, pear => 5, plum => 2); say $h{apple}; # 3 say join ",", sort keys %h; # apple,pear,plum say exists $h{pear} ? "yes" : "no"; # yes delete $h{plum}; for my $k (sort keys %h) { say "$k => $h{$k}"; # apple => 3 ... pear => 5 } ``` המלכודת: בדיקת חברוּת היא `exists $h{key}` (המפתח קיים) או `defined $h{key}` (המפתח קיים *וגם* ערכו אינו `undef`) - יחד הן מכסות את `array_key_exists` ו־`isset` של PHP. הפסיק השמן `=>` הוא מפריד המפתח/ערך הניבי ומצטט אוטומטית את ה־bareword שמשמאלו, כך ש־`apple => 3` אינו זקוק למירכאות סביב `apple`. ל־hash של Perl **אין סדר הכנסה** (בניגוד ל־array של PHP, השומר אותו); עברו `sort keys %h` כשאתם רוצים סדר יציב. כדי לעבור על מפתח וערך יחד, ה־`foreach ($h as $k => $v)` של PHP הופך או ל־`for my $k (keys %h)` או, כשאתם רוצים את שניהם בבת אחת, ל־`while (my ($k, $v) = each %h)`. ## הקשר זהו הפרק שכדאי לקרוא פעמיים. ל־PHP אין מקבילה, וכמעט כל סיפור ”Perl מוזרה“ מתחקה בחזרה אליו. כל ביטוי ב־Perl רץ או ב**הקשר רשימה** או ב**הקשר סקלר**, הנקבע לפי המקום שבו הוא מופיע, לא לפי מה שהוא. אותו מערך עצמו מתנהג אחרת בכל אחד מהם. השמה של מערך אל *סקלר* נותנת את אורכו; השמה שלו אל *רשימה* (קבוצת משתנים בסוגריים) מעתיקה איברים. ```perl use v5.42; my @list = (10, 20, 30); my $count = @list; # scalar context -> 3 (the length) say $count; # 3 my ($first) = @list; # list context -> first element copied say $first; # 10 my ($x, $y) = @list; # 10 and 20 say "$x $y"; # 10 20 ``` הסוגריים שמשמאל הם כל ההבדל. `my $x = @list` שואל ”כמה?“ ומקבל `3` - כאן שוכן `count($arr)`, רק שמאוית על ידי הקשר במקום פונקציה. `my ($x) = @list` שואל ”תן לי את האיברים“ ומקבל `10`. מפתח PHP קורא את שניהם כ“השם את המערך“, וזו בדיוק המלכודת. תת־שגרה יכולה לשאול באיזה הקשר היא נקראה, באמצעות `wantarray`, ולהחזיר דברים שונים בהתאם: ```perl use v5.42; sub items { return wantarray ? (1, 2, 3) : "three items"; } my @got = items(); # list context my $msg = items(); # scalar context say "@got"; # 1 2 3 say $msg; # three items ``` פונקציות מובנות משתמשות בזה גם כן. `scalar @array` מאלץ הקשר סקלר כדי לקבל מניין - זהו ה־`count()` המפורש שחיפשתם. התאמת regex עם `/g` מחזירה את רשימת ההתאמות בהקשר רשימה ואמת/שקר בהקשר סקלר. המלכודת: אין תשובה יחידה ל“מה המערך הזה עושה“ - זה תלוי בביטוי הסובב. כשמשהו מחזיר מספר שציפיתם שיהיה רשימה (או להפך), ההקשר הוא החשוד הראשון. פנו אל `scalar` כדי לאלץ מניין, ואל סוגריים משמאל כדי לאלץ העתקת איברים. ## אמיתיוּת ערכי השקר של Perl הם רשימה קצרה וספציפית: המספר `0`, המחרוזת הריקה `""`, המחרוזת `"0"`, ו־`undef` (ה־`null` של Perl). **כל השאר הוא אמת.** זה קרוב לכללי PHP אבל לא זהה, וההבדלים שקטים. ```perl use v5.42; for my $v (0, 1, "", "0", "00", "0.0", "false") { say "'$v' is ", ($v ? "true" : "false"); } ``` זה מדפיס, בדיוק: ```text '0' is false '1' is true '' is false '0' is false '00' is true '0.0' is true 'false' is true ``` המלכודת: `"00"` ו־`"0.0"` הם **אמת** ב־Perl, אף ש־PHP מתייחסת ל־`"0"` ורק ל־`"0"` כמחרוזת השקרית גם כן - Perl מסכימה על `"0"` אבל לא על דומיו. המחרוזת `"false"` היא אמת (זוהי מחרוזת לא־ריקה ולא־`"0"`), בדיוק כמו ב־PHP. ובניגוד לכלל המערך־הריק־הוא־שקרי של PHP, מערך ריק של Perl אינו בעצמו *ערך* שקרי: בהקשר בוליאני מערך מניב את אורכו, כך ש־`if (@list)` הוא אמת בדיוק כשהרשימה אינה ריקה, וזו הבדיקה שאתם רוצים. עבור ”השתמש בזה אם מוגדר, אחרת ברירת מחדל“, השתמשו ב־defined-or `//`, המקבילה ל־`??` של PHP, שנופל לערך החלופי רק על `undef` (לא על `0` או `""`): ```perl use v5.42; my %cfg = (host => "localhost"); my $port = $cfg{port} // 8080; # key absent -> undef -> 8080 say $port; # 8080 ``` ## זרימת בקרה מילות המפתח מוכרות; האיות נבדל בשתי דרכים קטנות. `elseif` הוא `elsif` (‏`e` אחד), ותנאים זקוקים לסוגריים בעוד בלוקים זקוקים לסוגריים מסולסלים - אין תחביר חלופי של `:`/`endif`. ```php if ($n < 0) { $sign = "neg"; } elseif ($n == 0) { $sign = "zero"; } else { $sign = "pos"; } foreach ($nums as $x) { echo $x; } while ($running) { step(); } ``` ```perl use v5.42; my $sign; if ($n < 0) { $sign = "neg" } elsif ($n == 0) { $sign = "zero" } else { $sign = "pos" } for my $x (@nums) { say $x } while ($running) { step() } ``` Perl מוסיפה שני דברים שאין ל־PHP. **משני סופית** מאפשרים למשפט פשוט לשאת עליו את התנאי או הלולאה שלו עצמו, מה שנקרא בטבעיות עבור שורות־יחיד: ```perl use v5.42; say $_ for @nums; # loop, one statement say "negative" if $n < 0; # guarded statement $sum += $_ for @nums; # accumulate ``` ו־`unless`/`until` הם הצורות השליליות של `if`/`while`, למקרים שבהם הניסוח החיובי נקרא במגושמות: `say "ok" unless $error`. המלכודת: האופרטור התלת־איברי הוא `?`/`:`, זהה לזה של PHP, ומשתשר באותו אופן: `my $sign = $n < 0 ? "neg" : $n == 0 ? "zero" : "pos"`. ל־Perl אין משפט `switch`/`match` בליבה; אופרטור תלת־איברי משורשר או hash של ניתוב (`$handler{$key}->()`) הוא הניב, ובלוק `for`־על־ערך־יחיד הוא התחליף הנפוץ ל־`switch`. ## תת־שגרות ה־`function` של PHP הופך ל־`sub` של Perl. ל־Perl המודרנית יש חתימות שנראות מאוד כמו רשימות הפרמטרים של PHP, כולל ערכי ברירת מחדל. ```php function greet($name, $greeting = "Hello") { return "$greeting, $name!"; } function total(...$nums) { return array_sum($nums); } ``` ```perl use v5.42; sub greet ($name, $greeting = "Hello") { return "$greeting, $name!"; } say greet("Ada"); # Hello, Ada! say greet("Rasmus", "Hi"); # Hi, Rasmus! sub total (@nums) { my $sum = 0; $sum += $_ for @nums; return $sum; } say total(1, 2, 3, 4); # 10 ``` ה־`...$nums` הווריאדי הופך לפרמטר מערך נגרר `@nums`, האוסף את כל הארגומנטים הנותרים. ארגומנטים על שם של PHP (או מוסכמת מערך ה־`$opts` הנפוצה) ממופים לפרמטר hash, מאחר שזוגות `key => value` משתטחים לרשימה שחתימת `%opts` אוספת: ```perl use v5.42; sub connect_to (%opts) { return "host=$opts{host} port=$opts{port}"; } say connect_to(host => "localhost", port => 8080); # host=localhost port=8080 ``` המלכודת: תת־שגרה מחזירה את ערך הביטוי האחרון שלה גם בלי `return`, וההחזרה היא **רגישת־הקשר** (ראו [הקשר]()). אם תכתבו `return @list`, הקורא מקבל את הרשימה בהקשר רשימה אבל את *המניין* בהקשר סקלר. היו מכוונים לגבי מה שאתם מחזירים כשהוא עשוי לשמש בכל אחת מהדרכים. ## הפניות מערכי PHP מקננים ישירות: `array` יכול להחזיק `array` אחר ואתם פשוט ממשיכים למנות. מערכים ו־hashים של Perl אינם מקננים ישירות; אתם מקננים אותם דרך **הפניות**, סקלרים המצביעים אל צבר. זהו אותו מנגנון כמו הפניות ה־`&$x` של PHP וידיות האובייקטים שלה, אבל ב־Perl אתם פונים אליו בכל פעם שאתם בונים נתונים מקוננים. קחו הפניה עם `\`, עקבו אחריה עם `->`. ```php $data = ["name" => "Ada", "langs" => ["Perl", "PHP"]]; echo $data["langs"][1]; // PHP $data["langs"][] = "Ruby"; ``` ```perl use v5.42; my $data = { name => "Ada", langs => ["Perl", "PHP"] }; say $data->{name}; # Ada say $data->{langs}[1]; # PHP push $data->{langs}->@*, "Ruby"; # postfix deref to push say "@{$data->{langs}}"; # Perl PHP Ruby ``` `{ ... }` בונה הפניית hash אנונימית ו־`[ ... ]` הפניית מערך אנונימית - אבני הבניין הליטרליות של נתונים מקוננים, התואמות את הליטרלים `[...]` ו־`[k => v]` שאתם כבר כותבים ב־PHP. החץ `->` עוקב אחר הפניה צעד אחד; בין שני מנויים הוא רשות, כך ש־`$data->{langs}[1]` ו־`$data->{langs}->[1]` זהים. החץ הוא גם בדיוק חץ קריאת־המתודה שאתם מכירים מ־OO של PHP - אותו סמל, גם עבור ”להגיע פנימה“ וגם עבור ”לקרוא למתודה“. תת־שגרות אנונימיות הן closures, בדיוק כמו ה־`fn` וה־`function() use (...)` של PHP, אבל ללא צורך בפסקת `use` - הן לוכדות לקסיקליים אוטומטית: ```perl use v5.42; my $double = sub ($n) { $n * 2 }; say $double->(21); # 42 ``` המלכודת: צורות ה־deref הסופיתיות (`->@*`, `->%*`, `->$*`) הן הדרך המודרנית והקריאה לבצע dereference לצבר שלם, ו־`"@{ ... }"` הוא האופן שבו אתם משחלפים מערך מ־dereferenced אל מחרוזת. הצורות הישנות יותר `@$ref` ו־`${$ref}{key}` עדיין עובדות ומופיעות בקוד קיים, אבל פנו אל צורות החץ והסופית בקוד חדש. ## ביטויים רגולריים ב־PHP ה־regex הוא משפחת הפונקציות `preg_*` עם מחרוזות תבנית כמו `'/\d+/'`. ב־Perl הוא **תחביר**: `=~` קושר תבנית למחרוזת, `m//` מתאים, `s///` מציב - אין מפרידים־בתוך־מחרוזת, אין קריאת פונקציה. קבוצות לכידה נוחתות ב־`$1`, `$2`, ולכידות על שם ב־`%+`. ```php if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $s, $m)) { [$_, $year, $month, $day] = $m; } $t = preg_replace('/,/', ';', "a,b,c"); $parts = explode(",", "one,two,three"); ``` ```perl use v5.42; my $s = "2026-06-20"; if ($s =~ /(\d{4})-(\d{2})-(\d{2})/) { say "$1 $2 $3"; # 2026 06 20 } if ($s =~ /(?\d{4})/) { say $+{year}; # 2026 } (my $t = "a,b,c") =~ s/,/;/g; # a;b;c my @parts = split /,/, "one,two,three"; # one two three ``` התאמת `/g` בהקשר רשימה מחזירה את *כל* הלכידות, המקבילה הישירה של `preg_match_all`: ```perl use v5.42; my @nums = "x1y2z3" =~ /(\d)/g; # 1 2 3 say "@nums"; ``` המלכודת: `s///` משנה את היעד שלו *במקום* ומחזיר את מספר ההצבות, לא מחרוזת חדשה - בניגוד ל־`preg_replace`, המחזיר מחרוזת טרייה. בגלל זה הדוגמה מעתיקה תחילה עם `(my $t = "a,b,c")` ואז מציבה על העותק - אחרת הייתם עורכים ליטרל מחרוזת. כדי לקבל מחרוזת חדשה בלי לגעת במקור (רפלקס ה־`preg_replace`), השתמשו בדגל `/r`: `my $t = $s =~ s/,/;/gr`. ## קלט/פלט של קבצים וזרמים הניב הוא ה־`open` בעל שלושת הארגומנטים עם ידית קובץ לקסיקלית, המקבילה של ה־`fopen` של PHP אל `$handle`. אופרטור הסוגריים המשולשים `<$fh>` קורא שורה, בתפקיד זהה ל־`fgets`. ```php $wh = fopen("/tmp/sample.txt", "w"); for ($i = 1; $i <= 3; $i++) { fwrite($wh, "line $i\n"); } fclose($wh); $rh = fopen("/tmp/sample.txt", "r"); while (($line = fgets($rh)) !== false) { echo "got: " . rtrim($line) . "\n"; } fclose($rh); ``` ```perl use v5.42; my $path = "/tmp/sample.txt"; open my $wh, '>', $path or die "cannot write: $!"; print {$wh} "line $_\n" for 1 .. 3; close $wh; open my $rh, '<', $path or die "cannot read: $!"; while (my $line = <$rh>) { chomp $line; say "got: $line"; # got: line 1 ... got: line 3 } close $rh; ``` כדי לבלוע את כל השורות בבת אחת, קראו את הידית בהקשר רשימה - המקבילה המוסרית של `file()`: ```perl use v5.42; open my $fh, '<', "/tmp/sample.txt" or die $!; my @lines = <$fh>; # all lines, including newlines say scalar @lines; # 3 ``` המלכודת: שורה הנקראת עם `<$fh>` שומרת על תו השורה החדשה שבסופה; קראו ל־`chomp` כדי להסירו (רפלקס ה־`rtrim`, אבל `chomp` מסיר רק את מפריד הרשומות). ו־`open` מחזיר שקר בכישלון במקום לזרוק או להזהיר, ולכן הניב `or die "...: $!"` הוא חובה - `$!` הוא הודעת שגיאת המערכת, ה־`errno` של Perl (ה־`error_get_last()` של PHP). ## מודולים וחבילות ה־`require`/`include` של PHP ומטעין־האוטו של Composer הופכים ל־`use` של Perl. מפריד מרחב השמות הוא `::`, לא `\`, ומודול `Foo::Bar` שוכן ב־`Foo/Bar.pm` על נתיב ההכללה `@INC` (ה־`include_path` של Perl). רשימת הייבוא של מודול היא בחירה של שמות להביא אל מרחב השמות שלכם. ```php require 'vendor/autoload.php'; use App\Util\ListHelper; $sum = ListHelper::sum(range(1, 10)); ``` ```perl use v5.42; use List::Util qw(sum0 max first); say sum0(1 .. 10); # 55 say max(3, 7, 2); # 7 say first { $_ % 2 == 0 } (1, 3, 4, 5); # 4 ``` מרחב השמות הסטנדרטי הענק והשטוח של פונקציות PHP (`count`, `in_array`, `array_map`, `implode`, `sprintf`, …) ממופה ב־Perl חלקית לאופרטורים מובנים וחלקית לכמה מודולים יומיומיים. התרגומים הנפוצים: | PHP | Perl | |------------------------|------------------------------------| | `count($a)` | `scalar @a` | | `in_array($x, $a)` | `grep { $_ eq $x } @a` | | `array_map($f, $a)` | `map { ... } @a` | | `array_filter($a, $f)` | `grep { ... } @a` | | `implode(",", $a)` | `join ",", @a` | | `explode(",", $s)` | `split /,/, $s` | | `sprintf(...)` | `sprintf(...)` (זהה) | | `isset($h[$k])` | `exists $h{$k}` / `defined $h{$k}` | | `array_sum($a)` | `sum0 @a` (`List::Util`) | `List::Util`, `Scalar::Util`, `POSIX`, `File::Spec`, ו־`Data::Dumper` הם המודולים היומיומיים; רוב מה שהייתם מושכים מ־Packagist מגיע במובנה או ב־CPAN, ארכיון החבילות של Perl. המלכודת: `use` רץ ב *זמן ההידור*, לא בנקודה בקובץ שבה הוא מופיע, ולכן סדר שורות ה־`use` ביחס לקוד שלכם אינו משנה כפי שמיקום `require` יכול לשנות ב־PHP. הייבוא גם הוא הצטרפות־מרצון לכל שם: `use List::Util qw(sum0 max)` מביא בדיוק את `sum0` ו־`max` אל מרחב השמות שלכם, ולא יותר - אין טבלת פונקציות גלובלית כפי ש־PHP חושפת כל פונקציה מובנית בכל מקום. (נתוני בקשת אינטרנט - המשתנים העל־גלובליים `$_GET`, `$_POST`, `$_SERVER` של PHP - אין להם מקבילה ברמת השפה ב־Perl; זו עבודתה של שכבת CGI או PSGI, לא של שפת הליבה, והיא מחוץ להיקף המדריך הזה.) ## אובייקטים עבור קוד חדש, ל־Perl 5.42 יש תחביר `class` ממדרגה ראשונה שייראה מוכר בהגעה מ־PHP. שדות מוצהרים עם `field`, מתודות עם `method`, ו־`$self` זמין בתוך כל מתודה בדיוק כפי ש־`$this` זמין ב־PHP (אינכם מציינים אותו כפרמטר). ```php class Point { public function __construct( public int $x = 0, public int $y = 0, ) {} public function to_string(): string { return "({$this->x}, {$this->y})"; } public function move(int $dx, int $dy): static { $this->x += $dx; $this->y += $dy; return $this; } } $p = new Point(x: 3, y: 4); echo $p->to_string(); // (3, 4) $p->move(1, 1); echo $p->to_string(); // (4, 5) ``` ```perl use v5.42; use feature 'class'; class Point { field $x :param = 0; field $y :param = 0; method to_string { return "($x, $y)" } method move ($dx, $dy) { $x += $dx; $y += $dy; return $self; } } my $p = Point->new(x => 3, y => 4); say $p->to_string; # (3, 4) $p->move(1, 1); say $p->to_string; # (4, 5) ``` הבנאי נוצר עבורכם, כמו תכונות הבנאי המקודמות של PHP: `:param` מסמן שדה שה־`new` הבנוי־אוטומטית מקבל כארגומנט על שם, ו־`= 0` הוא ברירת המחדל שלו. חץ קריאת־המתודה `->` הוא אותו אחד שאתם משתמשים בו ב־PHP. הורשה היא `:isa`, המקבילה של `extends`: ```perl use v5.42; use feature 'class'; class Point3D :isa(Point) { field $z :param = 0; method z { return $z } } my $q = Point3D->new(x => 1, y => 2, z => 3); say $q->to_string, " z=", $q->z; # (1, 2) z=3 ``` המלכודת: תכונת ה־`class` עדיין מסומנת כניסיונית ב־Perl 5.42, ועל `pperl` הודעות ”class is experimental“ מודפסות אל **stderr** אפילו עם `no warnings 'experimental::class'` בהיקף. הן אינן נוגעות בפלט התוכנית שלכם, ולכן סקריפט המדפיס אל stdout מתנהג בדיוק כפי שמוצג למעלה; פשוט צפו להודעות בזרם השגיאות. מודל האובייקטים הישן יותר - חבילה, hash שעבר bless לתוכה עם `bless`, ו־`@ISA` להורשה - הוא מה שתפגשו בקוד קיים וברוב CPAN; עבורו, עבור roles, ועבור עצות הגירה, ראו את [מדריך התכנות מונחה־העצמים](../oop/index.md). ## טיפול בשגיאות ל־`try`/`catch` של PHP יש מקבילה ישירה ב־Perl המודרנית, והיא נקראת כמעט זהה. הצד הזורק הוא `die` (ה־`throw` של PHP): ```php function risky($n) { if ($n < 0) { throw new Exception("negative!"); } return sqrt($n); } try { risky(-4); } catch (Exception $e) { echo "caught: " . $e->getMessage() . "\n"; } ``` ```perl use v5.42; use feature 'try'; sub risky ($n) { die "negative!\n" if $n < 0; return sqrt $n; } try { risky(-4); } catch ($e) { print "caught: $e"; # caught: negative! } ``` הצורה הישנה יותר, שעדיין נפוצה, עוטפת קוד ב־`eval { }` ובוחנת את `$@` (השגיאה שנתפסה) לאחר מכן - אותו דפוס כמו קוד PHP שנכתב לפני שחריגות היו ניביות: ```perl use v5.42; my $result = eval { risky(-1) }; if ($@) { print "caught: $@"; # caught: negative! } ``` המלכודת: `die` עם מחרוזת המסתיימת ב־`\n` מייצר בדיוק את ההודעה הזו; `die` עם מחרוזת ש *אינה* מסתיימת בתו שורה חדשה מקבל `" at SCRIPT line N.\n"` מצורף אוטומטית. סיימו את מחרוזות השגיאה שלכם ב־`\n` כשאתם רוצים הודעה נקייה, והשמיטו זאת כשאתם רוצים את עקבת הקובץ־והשורה. שימו לב גם לשני משתני השגיאה: `$@` הוא החריגה האחרונה שנתפסה (ה־`$e` של PHP), בעוד `$!` הוא שגיאת ה *מערכת* האחרונה (`open` שנכשל, מחרוזת ה־`errno`). ## ניבים כמה דפוסים שתראו מדי יום ותרצו באצבעות שלכם. מיון רשימה לפי מפתח מחושב (טרנספורם שוורץ - ה־`usort` של PHP עם מפתח מחושב מראש, נעשה בביטוי אחד: קישוט, מיון, הסרת קישוט): ```perl use v5.42; my @words = qw(apple fig pear); my @by_length = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ length $_, $_ ] } @words; say "@by_length"; # fig pear apple ``` בניית hash מרשימה, מקבילת ה־`array_combine`/`array_column` (כאן, מילה לאורכה): ```perl use v5.42; my @words = qw(apple pear fig); my %len = map { $_ => length $_ } @words; say "$_ => $len{$_}" for sort keys %len; # apple => 5 # fig => 3 # pear => 4 ``` ו־`pperl` הוא כלי שורת־פקודה בנישה של `awk`/`sed` - אותה עבודה שלשמה הייתם פונים אולי לסקריפט CLI מהיר ב־PHP. שורת־היחיד עם הלולאה המובלעת היא הרפלקס היומיומי של מפתח Perl: ```bash pperl -lane '$s += $F[-1]; END { print $s }' data.txt ``` המלכודת: קראו את [מדריך שורות־היחיד](../oneliner/index.md) עבור המתגים (`-n`, `-p`, `-a`, `-l`, `-e`) ועשרות המתכונים הבנויים עליהם; זהו אחד הדברים בעלי המנוף הגבוה ביותר ללמוד בהגעה מרקע של PHP ו־shell. ## מלכודות בהגעה מ־PHP המלכודות, אסופות לסריקה אחרונה לפני שאתם מתחילים לכתוב: - **Sigils משתנים עם השימוש.** איבר אחד של `@a` הוא `$a[0]`; ערך אחד של `%h` הוא `$h{k}`. PHP שומרת על `$` בכל מקום; Perl משנה את ה־sigil לפי הצורה שאתם מקבלים. - **`array` יחיד של PHP הוא שני טיפוסים של Perl.** החליטו מראש: מפתחות מספריים וסדר משמעם `@array`; מפתחות מחרוזת משמעם `%hash`. הם אינם ניתנים להחלפה זה בזה. - **שתי משפחות השוואה.** `==`/`<`/`<=>` למספרים, `eq`/`lt`/ `cmp` למחרוזות. בחרו לפי טיפוס האופרנד, לא לפי המשתנה. `==` על מחרוזות או `eq` על מספרים הוא הבאג הקלאסי. - **`.` מצרף מחרוזות, `+` רק מחבר מספרים.** ה־`+` של Perl לעולם אינו עושה איחוד מערכים או שרשור; ערבוב `.` ו־`+` משחית את הנתונים שלכם בשקט או מזהיר. - **`sort` ממוין כמחרוזות כברירת מחדל.** העבירו `{ $a <=> $b }` לסדר מספרי. - **ל־hashים אין סדר.** בניגוד ל־array של PHP, סדר המעבר אינו סדר ההכנסה; השתמשו ב־`sort keys %h` כשאתם זקוקים ליציבות. - **הקשר.** `my $x = @a` הוא *המניין*; `my ($x) = @a` הוא *האיבר הראשון*. הסוגריים הם כל ההבדל. קראו מחדש את [הקשר]() כשערך חוזר בצורה הלא־נכונה. - **הפתעות אמיתות.** `"0"` הוא שקר, אבל `"00"` ו־`"0.0"` הם **אמת**. רק `0`, `""`, `"0"`, ו־`undef` הם שקר. מערך ריק אינו ערך שקרי - `if (@a)` בודק אם אינו ריק. - **`s///` עורך במקום** ומחזיר מניין, בניגוד ל־`preg_replace`; השתמשו בדגל `/r` כדי לקבל מחרוזת חדשה במקום זאת. - **`open` אינו זורק.** השתמשו ב־`open ... or die "...: $!"`. - **הריצו `chomp` על קריאות השורה שלכם.** `<$fh>` שומר על תו השורה החדשה שבסוף. - **משתנים על־גלובליים של אינטרנט אינם חלק מהליבה.** `$_GET`/`$_POST` שייכים לשכבת CGI או PSGI, לא לשפה. - **מקור שאינו ASCII זקוק ל־`use utf8;`.** תחת `use v5.42;` בית שאינו ASCII כליטרל במקור שלכם הוא שגיאת הידור בלעדיו. שמרו על נתוני מחרוזות כ־ASCII אלא אם תוסיפו במפורש `use utf8;`. - **The `class` feature prints experimental notices to stderr** on `pperl`. Correct output, noisy error stream.