Czytany Wpis

Aboslutne podstawy Lua: sterowanie przepływem

W dzisiejszym wpisie poświęconym absolutnym podstawom Lua przyjrzymy się instrukcjom warunkowym oraz pętlom.

Aboslutne podstawy Lua: sterowanie przepływem

Dzisiejszy wpis dotyczy chyba najciekawszego z dotychczas poruszanych zagadnień, ale jednocześnie najtrudniejszego do opanowania, a przynajmniej tak mi się wydaje. Otóż poruszymy dzisiaj kwestię sterowania przepływem działania naszego programu i omówimy w tym zakresie dwa aspekty, czyli instrukcje warunkowe oraz pętle. Dzięki nim jesteśmy w stanie wprowadzić jakąś logikę do działania naszego programu opartą na wystąpieniu jakiś zdarzeń. Niektórzy uważają - być może nie bez podstaw - że właśnie te elementy odróżniają prawdziwe jezyki programowania jak właśnie Lua albo Python czy C++ od różnego rodzaju znaczników czy też zapytań, które z definicji są pozbawione możliwości wprowadzenia takiej logiki. Tym niemniej spory o to wyższości jednych nad drugimi zostawmy na boku.

Instrukcje warunkowe

Instrukcje warunkowe w swobodnej rozmowie osób orientujących się co nieco w kodowaniu często nazywane są “ifami”, ponieważ w większości języków programowania zaczynają się one właśnie od słowa kluczowego “if”, które w języku angielskim oznacza “jeżeli”. Oczywiście nie jest to reguła i zdarza się dość często, że oprócz wspomnianych “ifów” są nieco inne sposoby zapisu logiki warunkowego wykonywania kodu. Tym niemniej w przypadku Lua wielkich dylematów nie ma i podejście w tym zakresie jest “tradycyjne”, czyli całość zaczyna się od słowa if, po czym następuje inne standardowe słowo “then”, które zwyczajowo tłumaczy się “wtedy”, choć osobiście wolę określenie “wówczas”. Całość wygląda następująco:

if –tutaj mamy warunek, którego prawdziwość sprawdzamy

then

–kod, który jest wykonywany, jeśli warunek jest spełniony

end

W tej widocznej wyżej formie kod wykonywany jest wyłącznie, jeśli warunek, o którym mowa zaraz po słowie if, jest spełniony. Wówczas interpreter wykonuje, to co znajduje się między słowami then oraz end. W przeciwnym razie (warunek nie został spełniony) cały taki blok kodu jest ignorowany i interpreter idzie dalej, czyli przechodzi do wykonywania kolejnych wierszy po słowie end. Przykładowo moglibyśmy napisać program, który pyta się o imię użytkownika na początku swojego działania i dopiero w zależności od odpowiedzi albo przechodzi do realizacji standardowych czynności albo jakoś zmienia swoje działanie (na ten przykład wyświetla jakieś mniej lub bardziej zabawne powitanie typu “Biję pokłony przed Jaśnie Panem”) i dopiero w kolejnym kroku przechodzi dalej. Ewentualnie w przypadku każdej osoby, która przedstawi się jako Grzegorz kończy działanie całości, bo twórca aplikacji ma jakiś uraz wobec osób o tym imieniu.

Jednakże bardzo często możemy chcieć spełnić kilka warunków jednocześnie i choć pewnie można napisać kilka “ifów” następujących po sobie (choć nie zawsze to się może sprawdzić), ale istnieje wygodniejszy sposób. Dla przykładu w codziennym życiu o naszym ubiorze w danej chwili decydować może pogoda panująca za oknem. Wówczas nasza aplikacja w rodzaju asystenta od ubioru mogłaby wyglądać następująco:

local pogoda = "Słonko"

if pogoda == "Słonko"

then print("Bierz sandały")

elseif pogoda == "Deszczyk"

then print("Nakładaj kalosze")

else

print("A rób co chcesz")

end

Nasz kod zaczyna się od zdefiniowania zmiennej o nazwie pogoda. Następnia ta zmienna podstawiana jest do istrukcji warunkowej. Zwracam na użycie podwójnego znaków równania, który ponieważ pojedynczy -jak pamiętamy i widzimy wiersz wyżej - oznacza przypisanie wartości do jakiejś zmiennej. Dopiero jego podwojenie odpowiada równości jako takiej. Czyli na początku pytam się, czy zmienna pogoda równa się (==) słowu “Słonko”. Jeśli jest tak faktycznie wówczas na ekranie monitora zobaczymy napis “Bierz sandały”. Gdybyśmy jednak do zmiennej przypisali inną wartość, to interpreter sprawdza kolejne warunki. Jeśli byłoby to słowo “Deszczyk” to wyświetlałby polecenie obucia kaloszy. W każdym innym wypadku nasz asystent rozłoży ręce i powie - w pewnym sensie - że nie jest nam w stanie pomóc.

Użycie w tym i w wielu innych przypadkach słowa kluczowego else zapewnia nam ten komfort, że obsłużymy każdy scenariusz, czyli nawet taki, którego nie przewidzieliśmy  Po prostu wszystko, co nie kwalifikuje się do wcześniejszych warunków jest wrzucane do tego pojemnego wora.

Pętle

Pętle mają całkiem sporo wspólnego z instrukcjami warunkowymi, ponieważ często ich działanie (lub brak takiego działania) jest uzależnione od spełnienia pewnego początkowego warunku. Natomiast żadną miarą nie powinno się stawiać między nimi znaku równości. Po pierwsze nie każda pętla działa w ten właśnie sposób, ponieważ są takie ich warianty, które mają z góry określoną liczbę przebiegów niezależnie od spełnienia jakiś dodatkowych warunków. Po drugie, nawet jeśli pętla zaczyna się od sprawdzenia pewnych warunków początkowych, to jednak jej praktyczne zastosowania są zupełnie innej natury niż nawet najbardziej wyszukanych instrukcji warunkowych. No i niestety są mniej intuicyjne niż te ostatnie, zazwyczaj na początku sprawiają więcej trudności początkującemu programiście.

W Lua spotkamy się dwoma rodzajami pętli, czyli takimi, których wykonywanie jest uzależnione od prawdziwości jakiegoś warunku, oraz takimi, gdzie chcemy wykonać działania na wszystkich elementach jakiejś listy wartości. Tutaj zaczniemy od tego drugiego typu, czyli pętli for, ponieważ wydaje się ona łatwiejsza do zrozumienia.

W najbardziej ogólnej formie ma ona postać

forliczba początkowa, liczba końcowa, wielkość kroku (opcjonalnie)

do

–kod, który jest wykonywany określoną liczbę razy

end

Na początek należy wyjaśnić co oznaczają te parametry występujące bezpośrednio po słowie for. Pierwsza z nich wskazuję na liczbę, od której zaczyna się nasze odliczanie, druga określa koniec tej wyliczanki. Trzeci parametr również stanowi liczbę i mówi o tym jaki jest domyślny przeskok pomiędzy poszczególnymi elementami naszej wyliczanki. Nie jest on obowiązkowy i jeśli go nie podamy przeskok między kolejnymi liczbami z tego zakresu będzie zawsze o jeden.

Dla przykładu, jeśli do pętli wstawimy liczby 1 i 5 (for 1, 5), to w efekcie nasza lista będzie składała się z 5 elementów, czyli dostalibyśmy liczby 1, 2, 3, 4, 5. Jeśli jednak ustawimy wielkość kroku na 2 (for 1, 5, 2), wówczas lista będzie krótsza i będzie zawierał 3 pozycje (1, 3, 5). By się o tym przekonać wystarczy wypróbować następujący kawałek kodu:

print("Lista od 1 do 5 bez wybrania wielkości kroku")

for i=1, 5

do print(i)

end

print("A teraz ustawiamy krok co dwa")

for i=1, 5, 2

do print(i)

end

Oczywiście instrukcje do wykonania znajdujące się między słowami do oraz end mogą być i zazwyczaj są dużo bardziej złożone, natomiast ważne jest by zapamiętać, że pętla wykona je 5 razy w pierwszym przypadku (taką długość ma lista) a tylko 3 razy w drugim. Takie powtarzanie kodu fachowo nazywa się iteracją. Gdy pętla jest powtarzana, mówimy, że wykonuje iteracje.

Drugim rodzajem pętli jest ta z użyciem słowa kluczowego while i prawdopodobnie jest ona znacznie częściej używana w przypadku jakichkolwiek skryptów dla Robloxa. Pod pewnymi względami jest ona bardzo podobna do instrukcji warunkowych, aż można pokusić się o stwierdzenie, że to powtarzalna instrukcja warunkowa, choć to pewne uproszczenie i w praktyce jej dobre opanowanie wydaje się trudniejsze niż chociażby wspomnianej pętli for.

Rzecz jasna w przypadku pętli while używamy jej do powtarzania pewnego bloku kodu podobnie jak w przypadku pętli for, z tą różnicą że nie określamy i w teorii często nie wiemy, ile takich powtórzeń zostanie wykonanych, gdy uruchomiona zostanie nasza aplikacja (choć w niektórych przypadkach programiści z premedytacją posługują się pętlami wykonującymi się w nieskończoność).

Ogólny schemat takiej pętli wygląda następująco:

whiletutaj mamy warunek, którego prawdziwość sprawdzamy na początku każdego przebiegu pętli

do

kod, który jest wykonywany do czasu, gdy warunek początkowy jest spełniony

end

Podobnie jak instrukcje if, pętla while wykonuje znajdujący się w niej blok kodu (między słowami do oraz end) tak długo, jak ten początkowy warunek jest spełniony. Wygląda to w praktyce tak, że na początku interpreter sprawdza, czy początkowy warunek został spełniony i jeśli tak, to przystępuje do wykonania bloku kodu. Po zakończeniu jego wykonywania interpreter wraca na początek pętli i sprawdza, czy warunek początkowy jest nadal prawdziwy i jeśli tak się dzieje, znowu wykonuje zawarty w niej kod. Pętla ta będzie działała tak długo, aż przy którejś iteracji okaże się, że warunek przestał być w mocy. Zobaczmy to na prostym przykładzie:

local liczba = 1

while liczba < 5

do print(liczba)

liczba = liczba + 1

end

print("To już koniec naszej pętli")

Na początku zadeklarowaliśmy zmienną “liczba”, do której przypisaliśmy wartość 1. Dlatego gdy pętla startuje, to początkowy warunek jest spełniony, ponieważ liczba 1 niewątpliwie jest mniejsza od 5. Dlatego następuje wykonanie kodu, który ma za zadanie w pierwszej kolejności wyświetlić obecną wartość zmiennej, a potem dodaje do niej 1. Tym samym zmienna “liczba” ma teraz wartość równą 2. W tym momencie interpreter wraca na początek pętli i sprawdza warunek początkowy, który w przypadku liczby 2 nadal jest prawdziwy. Dlatego następuje kolejna iteracja. Dzieje się tak do momentu, gdy zmienna “liczba” osiągnie wartość 5. W tym momencie pętla kończy swoje działanie, zaś interpreter przechodzi dalej i wyświetla komunikat o końcu pętli.

Jak widać nie taka pętla straszna jak się może wydawać. Niestety w praktyce często aż tak pięknie nie jest i można napisać taki kawałek kodu, gdzie w pewnych okolicznościach pętla będzie działać w nieskończoność, choć nie taki był nasz zamiar. Dlatego w przypadku tego elementu programistycznego rzemiosła po prostu trzeba starać się być jak najbardziej przewidującym, co wymaga po prostu praktyki, do czego rzecz jasna zachęcam.

Na dzisiaj to by było tyle. W sumie to samo dotyczy tej serii wpisów poświęconych absolutnym podstawom Lua, ponieważ kolejne teksty będą dotyczyły tworzenia skryptów w środowisku Roblox Studio, choć zakładam, że przyda się ze dwa słowa napisać na temat programowania obiektowego, ponieważ de facto w dużej mierze na tym zasadza się programowanie w Robloxie (choć bardziej korzysta się istniejących klas i obiektów niż samemu się je tworzy). Tymczasem pozdrawiam wszystkich i do zobaczenia.