Przejdź do zawartości

Perl/Funkcje

Z Wikibooks, biblioteki wolnych podręczników.
Poprzedni rozdział: Modyfikatory
Następny rozdział: Ćwiczenia dla początkujących

Funkcja Perla to część kodu, która może być w łatwy sposób, wielokrotnie używana. Funkcje są kluczowym elementem organizującym we wszystkich, poza najmniejszymi, programach.

Wstęp

[edytuj]

Na razie pisaliśmy tylko kilka linii w Perlu na raz. Nasz przykładowy program zaczynał się na początku pliku i rozrastał się do końca pliku, z kilkoma skokami używającymi słów kluczowych sterujących wykonaniem, jak if, else i while. W wielu przypadkach jest jednak użyteczne dodanie dodatkowej warstwy do organizacji naszych programów.

Na przykład, kiedy coś idzie źle, typowy program wypisuje komunikat o błędzie. Kod wypisujący komunikat o błędzie może wyglądać następująco:

print STDOUT "cos poszlo zle!\n";

Inne komunikaty mogą wyglądać trochę inaczej, na przykład

print STDOUT "cos INNEGO poszlo zle!\n";

Możemy ozdobić nasz kod setkami linijek takich jak powyższe i wszystko będzie działało dobrze... przez pewien czas. Ale wcześniej czy później będziemy chcieli oddzielić komunikaty o błędach od "komunikatów stanu", które przekazują niegroźne informacje o naszym programie. Aby to uczynić, możemy poprzedzić wszystkie komunikaty o błędach słowem "BLAD" i wszystkie komunikaty o stanie słowem "STAN". Kod typowego komunikatu o błędzie zmieni się na

print STDOUT "BLAD: cos poszlo zle!\n";

Problem jest taki, że mając setki komunikatów o błędzie nie jest łatwo zmienić je wszystkie. Jest to sytuacja, kiedy podprogramy mogą pomóc.

Wikipedia definiuje podprogram jako "sekwencję instrukcji, która przeprowadza specyficzne zadanie jako część większego programu. Podprogramy mogą być wywoływane z różnych miejsc programu, w ten sposób pozwalając programom na dostęp do podprogramu wiele razy bez przepisywania kodu podprogramu więcej niż raz".

Cała ta kwiecista mowa oznacza, że możemy zebrać kod komunikatu o błędzie w jednym miejscu, jak poniżej:

sub wypisz_komunikat_bledu {
   my($komunikat) = @_;
   print STDOUT "BLAD: " . $komunikat . "\n";
}

Kiedy tylko coś pójdzie źle w naszym programie, możemy aktywować, lub wywołać (call), ten podprogram, z takim komunikatem, jaki tylko chcemy:

...
wypisz_komunikat_bledu("zdarzylo sie cos zlego");
...
wypisz_komunikat_bledu("zdarzylo sie cos naprawde strasznego");
...
wypisz_komunikat_bledu("zdarzylo sie cos denerwujacego");
...

Zobaczymy komunikaty typu

BLAD: zdarzylo sie cos zlego

Jeśli chcemy zmienić format naszych komunikatów o błędzie, aby, powiedzmy, zawierały kilka wykrzykników, wystarczy po prostu zmienić podprogram:

sub wypisz_komunikat_bledu {
   my($komunikat) = @_;
   print STDOUT "BLAD: " . $komunikat . "!!!\n";
}

Jest to na pewno prosty przykład i podprogramy mają kilka innych zalet, ale jest to skrótowy opis. Zasada jest jasna: napisz w jednym miejscu, popraw w jednym miejscu, zmień w jednym miejscu.

Teraz parę dalszych szczegółów na temat podprogramów. Będziemy dalej często używać następującego podprogramu, który, jeśli nie jest to jeszcze oczywiste, dodaje dwie liczby i zwraca ich sumę.

sub dodaj_dwie_liczby {
    my($x,$y) = @_;
    my $suma = $x + $y;
    return $suma;
}

Części podprogramu

[edytuj]

Oficjalna składnia definiowania podprogramu jest następująca:

sub NAZWA PROTOTYP ATRYBUTY BLOK

Jeśli ma to dla ciebie jakikolwiek sens, prawdopodobnie nie musisz czytać tego podręcznika.

Nazwa

[edytuj]
sub dodaj_dwie_liczby {
    my($x,$y) = @_;
    my $suma = $x + $y;
    return $suma;
}

Pierwsza linia funkcji zaczyna się od słowa kluczowego sub (od subroutine czyli po angielsku podprogram), po którym następuje nazwa funkcji. Każdy ciąg liter (bez polskich znaków) i liczb, który nie jest zarezerwowanym słowem Perla (jak for, while, if czy else) jest poprawną nazwą funkcji. Podprogramy, których nazwa określa, co robi dany podprogram, czynią kod programu łatwiejszym w czytaniu.

Prototyp (rzadko używany)

[edytuj]
sub dodaj_dwie_liczby($$) {

Opcjonalne ($$) określa, ilu argumentów oczekuje podprogram. ($$) mówi "ta funkcja wymaga dwóch wartości skalarnych". Z powodu złożoności implementacji Perla, prototypy nigdy nie osiągnęły pełnego potencjału i najlepiej je pomijać.

Ciało

[edytuj]

Ciało podprogramu wykonuje "pracę" i zawiera trzy podstawowe sekcje.

Czytanie argumentów

[edytuj]

Dane przekazywane do podprogramu są nazywane argumentami lub parametrami rzeczywistymi. Na przykład, w

dodaj_dwie_liczby(3,4)

3 i 4 są argumentami podprogramu dodaj_dwie_liczby.

Perl przekazuje argumenty do podprogramu jako tablicę reprezentowaną przez @_. Zazwyczaj jest wygodniej nadać znaczące nazwy tym argumentom, więc pierwsza linia funkcji wygląda najczęściej następująco:

sub dodaj_dwie_liczby {
    my($x,$y) = @_;  # czytanie argumentow
    my $suma = $x + $y;
    return $suma;
}

co umieszcza zawartość @_ w dwóch zmiennych nazwanych $x i $y. $x i $y są nazywane parametrami formalnymi. To rozróżnienie między parametrami formalnymi (argumentami) i parametrami rzeczywistymi jest subtelne i w większości nieistotne. Uważaj, aby nie pomylić specjalnej zmiennej $_ z @_, czyli tablicą argumentów przekazywanych do funkcji.

Pewne podprogramy nie wymagają żadnych argumentów, na przykład

sub hello_world {
   print STDOUT "Hello World!\n";
}

wypisze "Hello World" na STDOUT. Ten podprogram nie wymaga żadnych dodatkowych informacji o tym, jak wykonać swoją pracę i z tego powodu nie potrzebuje żadnych argumentów.

Większość nowoczesnych języków programowania ułatwia programiście życie jawnie dzieląc listę argumentów na zmienne. Niestety, Perl tego nie robi.

W programowaniu parametr oznacza niemalże to samo, co argument. Obydwu pojęć można używać zamiennie.

Ważna uwaga: zmienne globalne i lokalne
[edytuj]

W odróżnieniu od języków programowania takich jak C lub Java, wszystkie zmienne stworzone lub użyte w podprogramach Perla są domyślnie zmiennymi globalnymi. Oznacza to, że każda część programu poza twoim podprogramem może modyfikować te zmienne oraz że twój podprogram może, nie wiedząc o tym, modyfikować zmienne, których nie ma powodu zmieniać. W małych programach jest to często wygodne, ale gdy program staje się większy, prowadzi to często do złożoności i jest uważane za kiepski zwyczaj.

Najlepszym sposobem na uniknięcie tej pułapki jest umieszczenie słowa kluczowego my przed wszystkimi zmiennymi gdy pojawiają się po raz pierwszy. Mówi to Perlowi, że chesz, aby te zmienne były dostępne tylko wewnątrz najbliższej grupy nawiasów klamrowych zawierającej tę instrukcję. W efekcie te lokalne zmienne działają jako notatnik używany wewnątrz podprogramu, który znika, gdy podprogram kończy działanie. Linia use strict; na początku programu poinstruuje Perla, aby wymusił użycie my przed każdą zmienną, by uniemożliwić ci przypadkowe stworzenie zmiennych globalnych.

Alternatywą do my, którą możesz spotkać w starszych programach Perla jest słowo kluczowe local. local jest w pewnym stopniu podobne do my, ale trudniej jest go używać. Najlepiej przyzwyczaić się do my w swoich programach.

"Zasięg" opisuje, czy zmienna jest lokalna czy globalna i parę innych zależności. Informacji o nim możesz poszukać w Wikipedii lub innych źródłach.

Najciekawsza część funkcji

[edytuj]

W samym środku funkcji znajdziesz zapewne najbardziej interesujące "bebechy" ze wszystkich. W naszej funkcji dodaj_dwie_liczby jest to część, która odpowiedzialna jest za dodawanie:

sub dodaj_dwie_liczby {
    my($x,$y) = @_;
    my $sum = $x + $y;  # Najciekawsza część
    return $sum;
}

W środkowej części funkcji, pogrubionej w powyższym przykładzie, możesz zrobić co dusza zapragnie, rachunki, zapisywanie do plików, czy choćby wywoływanie innych funkcji.

Wyrażenie return

[edytuj]

Ostatecznie niektóre podprogramy "zwracają" (ang. "return") część przetworzonych informacji, używając słowa kluczowego return.

sub dodaj_dwie_liczby {
    my($x,$y) = @_;
    my $sum = $x + $y;
    return $sum;  # zwracanie wartości
}

Na przykład wyrażenie:

$sum = dodaj_dwie_liczby(4,5);

umieści w zmiennej $sum wartość 9, czyli sumę 4 i 5.

return może być także użyty bez żadnych argumentów, by opuścić podprogram zanim parser dotrze do końcowego }

sub hello {
    print "To zostanie wydrukowane\n";
    return;
    print "A to nie\n";
}

Odwoływanie się do podprogramów

[edytuj]

Podprogramy mogą być zdeklarowane gdziekolwiek w kodzie programu. Wywoływane mogą być korzystając ze składni:

dodaj_dwie_liczby(4,5); # najbezpieczniejsze podejście
dodaj_dwie_liczby 4, 5; # tylko jeżeli predeklarowana
&dodaj_dwie_liczby(4,5);  # stara składnia Perla, ale nadal ważna

Jeżeli prefiks & nie jest użyty branie w nawias jest wymagane, chyba że podprogram był predeklarowany

Funkcje wywołujące funkcje

[edytuj]

Funkcje są bardzo ważnym krokiem do efektywnego kodu, lecz dopiero łączenie funkcji wyzwala ich potęgę.

Jak być może podejrzewasz, wywoływanie funkcji wewnątrz innej nie różni się w żaden sposób od wywołania jej z części programu, która znajduje się poza wszelkimi "klamerkami".

Ta funkcja dodaje dwie liczby i mnoży je przez 3. Musisz ścierpieć jej bezużyteczność, kiedy będziesz budował swoje programy by rozwiązać unikalne problemy, natychmiast odkryjesz "moc" tego stylu programowania.

 sub dodaj_dwie_liczby_i_pomnoz_przez_trzy {
   my($x,$y) = @_;                       # wczytaj parametry
   my $sum = dodaj_dwie_liczby($x,$y);   # dodaj x i y, a wynik umieść w $sum
   my $sum_razy_trzy = $sum * 3;         # pomnóż przez trzy
   return $sum_razy_trzy;                # zwróć wynik
 }

Linia

    my $sum = dodaj_dwie_liczby($x,$y);   # dodaj x i y, a wynik umieść w $sum

wywołuje naszą funkcję odpowiedzialną za dodanie dwóch liczb i zwraca wynik do naszej zmiennej $sum. Rzecz to prosta...

W tej funkcji napisaliśmy za dużo kodu, niż w rzeczywistości potrzeba. Może być to mniejsze, choć równie czytelne:

 sub dodaj_dwie_liczby_i_pomnoz_przez_trzy {
   my($x,$y) = @_;                        # wczytaj parametry
   return 3 * dodaj_dwie_liczby($x,$y);   # zwróć dodane x i y, pomnożone przez 3
 }

Rekursywne wywoływanie funkcji

[edytuj]

Zobaczyliśmy funkcje wywołujące inne funkcje, lecz udanym pomysłem w programowaniu są funkcje wywołujące siebie. To nazywa się rekursją. Na pierwszy rzut oka następstwem rekursji może być przyczyną rzekomej nieskończonej pętli, lecz jest to z całą pewnością standard programowania.

W matematyce silnią nazywamy iloczyn wszystkich dodatnich liczb naturalnych nie większych niż dana liczba. Dla przykładu silnia z 5 (zazwyczaj pisana jako 5!) jest obliczana pomnożeniem przez siebie liczb 5, 4, 3, 2 i 1. Oczywiście jedynka nie zmienia wyniku. Funkcja silni może być przydatna do obliczania rzeczy typu liczby możliwych przypadków rozmieszczenia naszych gości przy stole.

Silnia jest oczywistym przykładem funkcji, która rekursywnie może być napisana równie prosto jak za pomocą pętli.

sub silnia {
  my($num) = @_;
  if($num == 1) {
    return 1;                         # zatrzymaj przy 1, nie mnóż przez zero
  } else {
    return $num * silnia($num - 1);   # wywołaj funkcje silni rekursywnie
  }
}

Samowywołująca siebie linia to:

return $num * silnia($num - 1);

która wywołuje funkcję silni z funkcji silni. Mogło by się tak dziać zawsze, gdybyśmy nie mieli swego rodzaju "znaku stopu", który temu zapobiega:

if($num == 1) {
  return 1;
}

To zatrzymuje sekwencje wywołań do silni i zapobiega nigdy niekończącej się pętli.

Działanie takiej funkcji można ująć w taki sposób:

silnia(5)
= 5 * silnia(4)
= 5 * 4 * silnia(3)
= 5 * 4 * 3 * silnia(2)
= 5 * 4 * 3 * 2 * silnia(1)
= 5 * 4 * 3 * 2 * 1 
= 120

Zaledwie jednak musnęliśmy rekursję. Dla niektórych problemów programistycznych jest to naturalne rozwiązanie. Dla innych jest to trochę... dziwne rozwiązanie. Wystarczy powiedzieć, że to narzędzie, które każdy programista powinien mieć w zanadrzu.

Podprogramy, funkcje, procedury

[edytuj]

Czytając tę lekturę mogłeś spotkać się z terminami "podprogramy", "funkcje", czy "procedury". Większość z nich była używana zamiennie, lecz ich docelowe znaczenie jest następujące:

Funkcja zawsze zwraca wartość. Pełno funkcji możemy znaleźć w takich klasach jak math.

Procedura może nie zwracać wartości. W odróżnieniu od funkcji, procedura zwykle oddziałuje z zewnętrznym środowiskiem jej argumentem. Na przykład, procedura może czytać i zapisywać do plików.

Podprogram wiąże się z sekwencją instrukcji, które mogą być albo funkcjami, albo procedurami.

Poprzedni rozdział: Modyfikatory
Następny rozdział: Ćwiczenia dla początkujących