Funkcje i zmienne.


Podstawową umiejętnością w programowaniu jest rozbijanie dużego algorytmu na mniejsze i prostsze zadania. Dla tych zadań można przygotować porcje kodu - funkcje (a na wyższym poziomie zaawansowania także klasy), które mogą być wielokrotnie używane w programie i ułatwią zapisanie całego algorytmu.
W tym rozdziale pokażemy jak zaprogramować:

  1. Najprostsze funkcje
  2. Zmienne
  3. Funkcje z argumentami
  4. Funkcje zwracające wynik

Najprostsze funkcje

Aby narysować kwadrat w poprzednim rozdziale użyliśmy pętli powtarzającej krok w przód i obrót. Wiele kwadratów można narysować umieszczając tą pętlę wewnątrz jeszcze jednej pętli repeat:

repeat 9 [
  repeat 4 [forward 100 right 90]
  right 40
  wait 100 ;wstrzymuje program na 1/10 sekundy
]

lub napisać funkcję rysującą kwadrat i w pętli umieścić wywołanie funkcji:

to kwadrat ; <--- definicja funkcji kwadrat
  repeat 4 [forward 100 right 90]
end        ; <--- koniec definicji funkcji

repeat 9 [
  kwadrat  ; <--- wywołanie funkcji
  right 40
  wait 100 ;wstrzymuje program na 1/10 sekundy
]

Obie wersje programu zadziałają identycznie, lecz kod pętli z funkcją jest bardziej klarowny - to bardzo ważne w programowaniu. Funkcję kwadrat można też będzie wykorzystać w dowolnym innym miejscu programu, a jeśli trzeba będzie kiedyś zmienić sposób rysowania wszystkich kwadratów w programie (np.: zmienić ich kolor) - wystarczy to zrobić w jednym tylko miejscu: w treści funkcji kwadrat.
Instrukcja wait wstrzymuje program na zadaną liczbę milisekund (1 sekunda = 1000 milisekund). Pomoże to zaobserwować postępy rysowania kwadratów w pętli.

Zmienne

Do zapamiętania wartości (liczb, napisów, tablic...) w trakcie działania programu służą zmienne. Zmienne w LOGO to bardzo obszerny temat, teraz przedstawimy tylko podstawy niezbędne do dalszej pracy z funkcjami.
Zmienne w LOGO (a więc także w POOL) posiadają trzy ważne cechy:

  • zmienne mogą zostać utworzone w dowolnym miejscu kodu programu;
  • jedna zmienna może przechowywać dane różnego rodzaju (jej typ jest dynamiczny), np. do zmiennej przechowującej początkowo liczbę można później przypisać napis;
  • LOGO stara się dopasować typ zmiennej (oraz całych wyrażeń) do typu oczekiwanego w danym wyrażeniu, np. dodawanie liczby 5 i napisu o treści "5" jest poprawne, a wynikiem jest liczba 10.

Do utworzenia zmiennej będziemy teraz używali instrukcję make. Jej argumentami są: nazwa zmiennej i jej początkowa wartość. Ta sama instrukcja służy do przypisania nowej wartości do zmiennej, która już istnieje.

Ważne informacje:

  • nazwa zmiennej jest słowem, czyli ciągiem znaków; w LOGO ciąg znaków rozpoczyna się od znaku cudzysłowu, np.: "nazwa
  • aby odczytać wartość zmiennej należy użyć instrukcji thing "nazwa; ponieważ odczytywanie zmiennych jest bardzo częstą operacją, instrukcja thing "nazwa ma skrócony odpowiednik: :nazwa (zauważ, że tu nazwa występuje bez cudzysłowu);
  • w POOL możemy użyć bardziej naturalnego zapisu: odpowiednikiem make "nazwa wartość jest "nazwa := wartość

Oto kilka linijek programu, który ilustruje operacje na zmiennych:

make "x 1      ;1) tworzy zmienną "x" o wartości 1
print :x       ;2) wypisuje wartość zmiennej "x"

make "y :x + 1 ;3) tworzy zmienną "y" o wartości 2
make "x 2 * :y ;4) przypisuje zmiennej "x" nową wartość: 4
(print :x :y)  ;5) wypisuje wartości zmiennych "x" i "y"

"z := :x + :y  ;6) prostszy zapis - używany w POOL
print :z

make "x "10       ;7) nowa wartość zmiennej "x" to napis "10"
(print "słowo :x;8) wypisuje napis "słowo 10"
print 2 * :x      ;9) wypisuje wynik mnożenia: 20

Wynik działania programu (w oknie tekstowym):

1
4 2
6
słowo 10
20

W programie rysującym kwadraty możemy dodać zmienną :n aby ułatwić zmianę liczby kwadratów:

to kwadrat
  repeat 4 [forward 50 right 90]
end

make "n 20 ;spróbuj także: 40, 10, 5
repeat round 360 / :n [
  kwadrat
  right :n
]

Funkcje z argumentami

Nasza funkcja zawsze rysuje kwadrat o długości boku 100, co ogranicza jej użyteczność. Można to usprawnić i długość boku przekazywać jako argument funkcji - tak, jak do tej pory podawaliśmy np. długość kroku w instrukcji forward 100 czy wartość kąta w obrocie right 90. Argument funkcji należy określić w jej definicji. W treści funkcji odwołujemy się do argumentu tak jak do zmiennej:

to kwadrat :a ; <--- definicja funkcji z argumentem
  repeat 4 [forward :a right 90]
  print :a    ; <--- wypisz wartość argumentu
end

; wywołania funkcji z argumentem:
kwadrat 50
kwadrat 100
kwadrat 150

;print :a  ; <--- ta instrukcja spowoduje błąd

Każde wywołanie funkcji w powyższym programie rysuje kwadrat o zadanym boku. Funkcja wypisuje także wartość argumentu w oknie tekstowym.
Zauważ: ostatnia, zakomentowana linia używa tej samej instrukcji co funkcja: print :a. Włączenie tej linii (usunięcie pierwszego znaku ;) spowoduje błąd w wykonaniu programu - spróbuj. Argument funkcji jest zmienną lokalną - oznacza to, że jest dostępny tylko wewnątrz funkcji, w której został zdefiniowany.

Poniższy program korzysta z funkcji kwadrat o zmiennej długości boku. Zwróć uwagę, że argument w wywołaniu funkcji jest wynikiem wyrażenia: 3 * repcount. Instrukcja repcount jest często używana w pętlach LOGO - jej wynikiem jest numer iteracji pętli.

to kwadrat :a ; <--- definicja funkcji z argumentem
  repeat 4 [forward :a right 90]
end

repeat 50 [
  kwadrat 3 * repcount ; <--- wywołanie funkcji
  right 1
]

Więcej argumentów

Funkcja może mieć wiele argumentów - to proste, w definicji funkcji wystarczy dodać kolejne argumenty. Co więcej, funkcje i instrukcje mogą posiadać argumenty opcjonalne, np.: instrukcja print, która wypisuje wartości argumentów w oknie tekstowym, domyślnie używa jeden argument, np.:

print "ok

ale może też być użyta z wieloma argumentami, których wartości zostaną wypisane w jednej linii - w tym celu instrukcję i jej argumenty należy umieścić w nawiasach, np.:

(print 1 "ok 2+3)

Podobnie do instrukcji także funkcje mogą posiadać argumenty opcjonalne. Jeśli w wywołaniu takiej funkcji chcemy przekazać wartość argumentu opcjonalnego, całe wywołanie należy umieścić w nawiasach. Jeśli wartość argumentu opcjonalnego nie zostanie podana w wywołaniu - funkcja nada mu wartość domyślną.
Argument opcjonalny umieszcza się w definicji funkcji razem z jego wartością domyślną w nawiasach kwadratowych: to funkcja :arg_obowiązkowy [:arg_opcjonaly wartość].
Poniższy program definiuje funkcję kwadrat, która ma dwa argumenty obowiązkowe :a i :kolor oraz argument opcjonalny :p o domyślnej wartości 100.

to kwadrat :a :kolor [:p 100] ;rysuje wypełniony kwadrat:
  (setpc :kolor :p)           ;     a - długość boku
  setps :a                    ; kolor - kolor kwadratu
  forward :a                  ;     p - krycie kolorem (domyślnie 100%)
end

hideturtle      ;ikonka żółwia jest ukryta
setpalette "hot ;użyj palety kolorów o nazwie "hot"

make "n 5
make "l 10 ;początkowy rozmiar kwadratu
make "m 1 + round 360 / :n

repeat :m [
  make "k 1000 * repcount / :m ;oblicz numer koloru w palecie

  ;kwadrat :l :k               ; <--- tu :p ma wartość domyślną 100%
  (kwadrat :l :k 20)           ; <--- tu ustawiamy :p na 20%

  pu bk :l - 4 pd   ;cofnij żółwia z podniesionym pisakiem
  make "l :l + 1.7  ;oblicz rozmiar kwadratu
  right :n   ;spróbuj użyć tej linii...
  ;right :m  ;...lub tej zamiast powyższej
]

Funkcje zwracające wynik

Funkcje zdefiniowane przez programistę mogą zwracać wynik, podobnie jak instrukcje LOGO takie jak licznik iteracji pętli repcount lub pierwiastek z liczby sqrt x. Oto przykład:

;................parametry rysowania...................
:pool_cfg,"scale_x := 30 ;skala rysowania w poziomie
:pool_cfg,"scale_y := 30 ;skala rysowania w pionie
window                   ;żółw może wychodzić poza okno
hideturtle               ;ikonka żółwia jest ukryta
;......................................................

to pitagoras :a :b         ;trójkąt prostokątny
  let "p pos               ;zmienna lokalna zapamiętuje pozycję żółwia
  left 90 forward :a
  right 90 forward :b
  setpos :p                ;powrót do początkowej pozycji
  output sqrt :a^2 + :b^2  ;wynik: przeciwprostokątna
end

print sqrt 3 * 3 + 4 * 4  ;oblicz na piechotę...
print pitagoras 3 4       ;...lub z funkcją

Program definiuje funkcję dwóch argumentów: :a i :b. Funkcja ta rysuje trójkąt prostokątny, w którym długości przyprostokątnych odpowiadają wartościom argumentów. Wynikiem funkcji jest liczba oznaczająca długość przeciwprostokątnej w tym trójkącie - instrukcja output pozwala przekazać wartość wyniku do wyrażenia wywołującego funkcję. W tym przykładzie output jest ostatnią instrukcją funkcji, ale można umieścić tą instrukcję także wewnątrz treści funkcji - spowoduje wtedy przerwanie działania funkcji (podobnie jak instrukcja stop).
Zauważ, że funkcja zapamiętuje początkową pozycję żółwia w zmiennej :p, by po narysowaniu przyprostąkątnych wrócić do tej pozycji rysując przeciwprostokątną. Zmienna :p jest zmienną lokalną i podobnie jak argumenty :a i :b jest dostępna tylko wewnątrz funkcji. W POOL do utworzenia zmiennej lokalnej i nadania jej wartości służy instrukcja let. W wielu dialektach LOGO instrukcja ta występuje pod nazwą localmake - POOL akceptuje także tą nazwę.
Fragment programu oznaczony jako "parametry rysowania" zawiera kod określający rozmiar kroków żółwia w pikselach ekranu oraz sposób rysowania.

Zakończenie

Umiejętność programowania funkcji będzie bardzo przydatna w następnych rozdziałach. Pokażemy w nich jak napisać funkcję rekurencyjną - czyli taką, która wywołuje samą siebie. Pokażemy też jak napisać program, w którym jednocześnie działa wiele żółwi-obiektów i jak użyć funkcji do opisu ich zachowania.