פרק 5: פונקציות והוראות

[הקודם : טיפול במחרוזות][חזרה לתוכן העניינים][הבא : טיפול בקבצים]

5.1 משתני ברירת מחדל

פרל עובדת דרך משתני ברירת מחדל. תכירו אותם: $_, @_, שהם המערך וסקלר ברירת המחדל. הכי טוב להראות דוגמא. ארבעת זוגות הפקודות הבאות זהות:

$m = shift(@_);
$m = shift; 
print $_;
print;
@_=split($_);
split;
chomp($_);
chomp;

כזכור, הפקודה shift לוקחת מערך ומורידה לו איבר בתחילתו. אם אתה לא אומר לפקודה לאיזה מערך אתה מתכוון, היא מניחה שאתה מדבר על @_. גם print, אם אתה לא אומר לה מה להדפיס, מדפיסה את $_. וגם chomp, כשלא אומרים לה לאיזה משתנה לבצע, היא מניחה שמתכוונים ל-$_. אבל split היא בכלל מקרה מיוחד. אם לא אומרים לה את מה לחתוך, היא מניחה שמתכוונים ל- $_. אם לא אומרים לה גם איפה לשים את המערך שהיא הרגע יצרה, היא שופכת אותו לתוך @_. (למעשה, ההתנהגות הזאת לא מומלצת, כי בטעות אתה עלול לדרוס בקלות את @_. אם תשתמש בה, תקבל אזהרה)
במבט ראשון זה מוזר. במבט שני זה מוזר. במבט שלישי, יש לזה פוטנציאל. אם כל הפונקציות והאופרטורים יעבדו ככה, לא נצטרך בכלל משתנים בקטעי קוד מסוימים.
מצד שני כתבתי כבר אלפי שורות קוד, וחוץ מהמקומות שמחייבים זאת, לא השתמשתי בהם.
בתיעוד של כל פונקציה (לדוגמא: perldoc -f shift) כתוב בפרוטרוט מה הפונקציה רוצה לקבל, ומה יקרה אם היא לא תקבל את זה. לא כל הפונקציות משתמשות במשתני בירת המחדל, לכן יש לעיין במדריך לצרכן לפני השימוש.

5.2 כתיבת פונקציות

פונקציה היא חתיכת קוד, המקבלת את הפרמטרים שלה בתוך המערך הדיפולטי, @_, ומחזיר לתוכנית הקוראת מערך או משתנה, (דרך המערך או המשתנה הדיפולטי) בעזרת הפקודה return.
סימן הזיהוי של סוג הפונקציה שאני אראה כאן הוא התו '&', אבל אף אחד לא משתמש בו כי אנחנו שמנים ועצלנים. (ופרל בד"כ מסוגל לנחש בעצמו שאתה מתכוון לקרוא לפונקציה בשם זה) השם הרשמי של הסוג הזה הוא List-Operator, זה כי היא מקבלת מערך בתור קלט.
דוגמא:

sub myChomp { 
    $_=shift; 
    chomp($_); 
    return $_; 
} 
my $text="And the winner is\n";
print myChomp($text);
chomp($text);
print $text, "\n";

הפקודה shift מוציאה את האיבר הראשון מתוך @_ ושמה אותו בתוך $_. הפקודה chomp, שכבר הכרנו אותה בפרק הקודם, בלי פרמטרים היא עובדת על $_. ואז, אנו מחזירים את $_ לתוכנית הקוראת. (תנסה גם להוסיף את התו '&' לפני myChomp בשורה השביעית, ותראה שפשוט לא תהיה לזה השפעה)
ננסה עוד דוגמא:

sub myTime { 
    my ($msg1, $i, $msg2) = @_; 
    print $msg1, (localtime)[$i]+1, $msg2, "\n"; 
}

אם נכתוב בגוף התוכנית:

myTime("Today is the ",6," day of the week");

נקבל: (בהנחה שמדובר ביום שלישי)

Today is the 3 day of the week

שים לב שפה לא החזרנו שום דבר. קורה. פונקציה לא חייבת להחזיר משהו. יכולה להחזיר מערך, סקלר, או כלום. ניקח שוב את הדוגמא האחרונה:

sub myTime { 
    my ($msg1, $i, $msg2) = @_; 
    print $msg1, (localtime)[$i]+1, $msg2, "\n"; 
    return ($msg1, (localtime)[$i]+1, $msg2); 
}

פה היא מחזירה מערך, המכיל את שתי ההודעות שהתקבלו ואת הערך של localtime המבוקש. אבל אם נכתוב:

my $ans = myTime("Today is the ",6," day of the week");

התוצאה היא ש-$ans=3. זה בגלל שהחזרנו מערך, ואם עושים סקלר שווה מערך, מקבלים בסקלר את גודל המערך. כדי שהפונקציה תדע מה התוכנית רוצה ממנה, משתמשים בשאלה wantarray.
ככה:

sub myTime { 
    my ($msg1, $i, $msg2) = @_; 
    print $msg1, (localtime)[$i]+1, $msg2, "\n"; 
    if (wantarray) { 
        return ($msg1, (localtime)[$i]+1, $msg2); 
    } else { 
        return join(" ", $msg1, (localtime)[$i]+1, $msg2); 
    } 
}

אם רוצה מערך, נותנים לו מערך. אם רוצה סקלר, נותנים לו סקלר. בדיוק כמו ש-localtime יודע מה רוצים ממנו, ככה הפונקציה שלך.
הערה: בטח כבר הפנמת שהמשתנים עוברים דרך מערך. זה אומר, שבעצם אפשר להעביר רק מערך יחיד. הנה צורות קריאה נכונות ולא נכונות:

myFunc1($str, $i, @kaskas); #right. 
myFunc2($str, @kaskas , $i); #wrong.

באפשרות השניה, כאשר תעשה בתחילת הפונקציה:

my ($str, @kaskas , $i) = @_;

אזי $str יקבל את שלו, אבל @kaskas ישתה את שאר המשתנים, ו-$i יקבל undef. לכן, ככה אפשר להעביר לפונקציה כמה סקלרים, אך רק מערך אחד, בסוף הרשימה.
אתה בטח עכשיו אומר, אבל יש פונקציות שמקבלות מערך בהתחלה, ואח"כ סקלרים ומערכים נוספים, כמו splice. זה נכון, אבל הם עובדים קצת אחרת. לא ניכנס לנושא, כי זה גורר נושאים אחרים כמו מצביעים. נסתפק בפונקציות רגילות, כאלה.
ולסיום, משהו קטן: דרך פשוטה לעשות ערכי ברירת מחדל לפרמטרים:

sub write_person {
    my ($first, $last, $age, $address) = @_;
    $age ||= 23;
    $address ||= "the galaxy";
    my $msg = "$first $last is $age years old ";
    $msg .= "and lives in $address";
    return $msg;
}
print write_porson("John", "Rahul", 17), "\n";

הפונקציה רוצה לקבל ארבעה סקלרים - שני ערכים חובה, ושניים אופציונליים. כל ערך שלא יתקבל יהיה undef, ולכן יוכנס לו את הערך הדיפלטי.

5.3 הוראות לקומפילר

כשמריצים סקריפט פרל, הוא עובר שני שלבים: קומפילציה והרצה. אפשר לתת הוראות לקומפילר של פרל, ולהסביר לו מה אתה רוצה. לדוגמא, השורה מהתוכנית בפרק הראשון:

use strict;

הפקודה אומרת לקומפילר, שמפה והלאה הוא עובד במצב strict, מה שאומר כל מיני הגבלות. ההוראה תהיה תקפה עד סוף הבלוק בו היא נכתבה. נדגים על הוראה אחרת:

my $i; 
for ($i=1; $i<=10; $i++) { 
    use integer; 
    print $i/3, "\n"; 
} 
print "Final: ", $i/3, "\n";

ההוראה use Integer אומרת לקומפילר שמפה והלאה, להשתמש בקוד עבור מספרים שלמים. זאת אומרת, שהתוצאה תהיה 0,0,1,1,1,2,2,2,3,3. אבל ברגע שנגמרת הלולאה, הפקודה לא תקפה יותר, וההדפסה האחרונה תהיה 3.333. כשאתה כותב use strict בתחילת התוכנית, אתה בעצם מחיל אותו על כל התוכנית. זה בגלל שהוא נכתב בהתחלה, מחוץ לכל בלוק. אם אתה רוצים לבטל את ההוראה לקטע מסוים, אזי תשתמש בפקודה no. לדוגמא: no strict.

no strict; 
for (; $bla<28; $bla+=3) { 
    print $bla, "\n"; 
}

אוטומטית יוגדר משתנה חדש, $bla, יאותחל ל-undef, ויורץ בלולאה. בסיבוב הראשון נקבל שתי התראות שאנחנו עושים פעולות על משתנה לא מאותחל. אחרי הפעם הראשונה ש-$bla מקודם בשלוש, הערך שלו יהיה שלוש, והלולאה תמשיך לרוץ. זה שימושי, אם אתה רוצה לעשות באיזה חלק תוכנית דברים שלא יעברו ב-strict, אבל יעשה לך חיים פשוטים. כמובן שלא למדנו (ולא נלמד) במדריך הזה שום דבר כזה, אבל שתדע שהאופציה קיימת. הוראות נוספות במערכת:

use integer; 
no integer;

מורה לקומפילר לעבוד עם מספרים שלמים בלבד.

use constant PI => 3.14; 
print PI *10, "\n";

הגדרת קבוע. שים לב שכאשר משתמשים בקבוע, לא צריך את סימן ה-$. אפשר גם להגדיר קבוע מערך:

use constant ARRAY => ( 1,2,3,4 ); 
print ARRAY; 
print((ARRAY)[3]);

הגדרת הקבוע כופה הקשר רשימה מצד אחד, אך לא מערך. (תעיף מבט על ההסבר על המערכים בפרק 2, זה מוזכר) כלומר, כאשר הגדרנו את PI, בעצם הגדרנו רשימה שמה יש מספר אחד, והוא 3.14. ואז בפקודת ה-print הסתכלנו עליו בהקשר סקלרי, מה שנותן את האיבר האחרון (והיחיד, במקרה הזה) של הרשימה. מצד שני, כשרצינו להציג את האיבר השלישי של ARRAY, כדי לקבל מערך "אמיתי" היינו צריכים לכלוא את הקבוע בסוגריים. הצץ בעזרה של פרל:

perldoc constant

עבור עוד הרבה דוגמאות.

5.4 תוכנית דוגמא

זוהי תוכנית דוגמא, המקבלת רשימת מחרוזות ומחזירה אותם אחרי חיתוך הרווחים (או כל רווח-לבן) בהתחלה, הרווחים בסוף, וצמצום רווחים רצופים לרווח יחיד. כל זה, ללא שימוש בביטויים רגולריים.

#!/usr/bin/perl -w 
use strict; 
my @strs = ("first last", " somespace more space ", "\t\t include\ttabs\t \t as well\t \t", " "); 
my @str = @strs; 
foreach my $s (@str) { 
    print "'$s'\n"; 
    $s = remove_leading_space($s); 
    print "'$s'\n"; 
    $s = remove_trailing_space($s); 
    print "'$s'\n"; 
    $s = reduce_space($s); 
    print "'$s'\n"; 
    print "=======================\n"; 
} 
sub remove_leading_space { 
    my $s = shift; 
    my $i=0; 
    my $l = length $s; 
    while ($i < $l) { 
    my $c = substr($s, $i, 1); 
    last if (not is_white_space($c)); 
    $i++; 
} 
substr($s, 0, $i, ''); 
    return $s; 
} 
sub remove_trailing_space { 
    my $s = shift; 
    my $i=0; 
    my $l = length $s; 
    while ($i < $l) { 
        my $c = substr($s, - $i -1, 1); 
        last if (not is_white_space($c)); 
        $i++; 
    } 
    substr($s, -$i, $i, ''); 
    return $s; 
} 
sub reduce_space { 
    my $s = shift; 
    my $i = 0; 
    while ($i < length($s)-1) { 
        my $c = substr ($s, $i, 1); 
        if (not is_white_space($c)) { 
            $i++; 
            next; # skip if not white space 
        } 
        $c = substr ($s, $i+1, 1); 
        if (not is_white_space($c)) { 
            substr($s, $i, 1, ' '); # make sure it is a space 
            $i+=2; 
            next; # skip two if next is not white space 
        } 
        substr($s, $i, 1, ''); # cut off one white space 
        #$i++; 
    } 
    return $s; 
} 
sub is_white_space { 
    $_=shift; 
    return (($_ eq " ") || ($_ eq "\t") || ($_ eq "\f") || ($_ eq "\n") || ($_ eq "\r")); 
}

הנה תוכנית שעושה את אותו הדבר, רק בעזרת פקודות החלפה.

my @str = @strs; 
foreach my $s (@str) { 
    print "'$s'\n"; 
    $s =~ s/^\s+//; 
    print "'$s'\n"; 
    $s =~ s/\s+$//; 
    print "'$s'\n"; 
    $s =~ s/\s+/ /g; 
    print "'$s'\n"; 
    print "=======================\n"; 
}

והנה תוכנית שלישית שעושה את אותו הדבר, גם בלי ביטויים רגולריים, תוך ניצול תכונה מיוחדת של פקודת split כשהיא פועלת עם פרמטר חיתוך רווח.

for my $s (@str) { 
    $s = join ' ', split ' ', $s; 
    print "'$s'\n"; 
}
[הקודם : טיפול במחרוזות][חזרה לתוכן העניינים][הבא : טיפול בקבצים]

נכתב ע"י שמואל פומברג, כל הזכויות שמורות © ראה פרק 1.5 לתנאי רשיון