Niby banał, ale czy aby na pewno? Nie pytam o edge-cases (jak pusta pętla for będąca pętlą while true de facto) ale czy na pewno je rozumiemy? Sprawdźmy to.
Oto pętla do-while z cli, które prosi o prawidłowy URL (tabem jeszcze podpowiedzi wyświetla):
<?php
function completion_function($line, $index) {
return [
"http://",
"https://"
];
}
readline_completion_function('completion_function');
do {
$url = readline("Enter URL:");
} while (!(filter_var($url, FILTER_VALIDATE_URL)) );
file_put_contents("php://output", $url);
?>
Dla tego ćwiczenia zrobimy coś, czego ogólnie robić nie wolno, czyli wykorzystamy goto. Swoją drogą nie lubię takich stwierdzeń typu „nie używaj goto”. One sprawiają, że ludzie nie interesują się programowaniem, tylko bezmyślnie kopiują skrypty do frameworków niczym ChatGPT.
Goto nie należy używać w kodzie produkcyjnym. Bo flow programu staje się niezrozumiałe. Chyba że chcemy wyskoczyć z kilku poziomów zagnieżdżenia pętli – to raz.
Dwa, goto należy używać. Goto wykonuje jump, skok, w asemblerze non-stop jakieś goto jest używane. Można się uczyć asm, żeby zrozumieć jak działa pętla, można poprzestać na C, można wreszcie w całkiem przystępnym PHP się tego nauczyć.
Później zdecydujemy, czy zadowala nas wiedza jak działa pętla, czy będziemy dalej temat drążyć.
Tym niemniej:
<?php
function completion_function($line, $index) {
return [
"http://",
"https://"
];
}
readline_completion_function('completion_function');
start:
$url = readline("Enter URL:");
if(!(filter_var($url, FILTER_VALIDATE_URL))){
goto start;
}
file_put_contents("php://output", $url);
?>
Najpierw wykonujemy akcję. Później sprawdzamy warunek. Dopóki nie zostanie spełniony: zapętlamy się, od akcji zaczynając.
Teraz pętla while:
<?php
$arr = ["a", "b", "c"];
while(!empty($arr)){
echo array_pop($arr) . PHP_EOL;
}
echo "finished" . PHP_EOL;
Spróbujmy napisać jej odpowiednik z goto:
<?php
$arr = ["a", "b", "c"];
start:
$cond = !empty($arr);
if(!$cond){
goto exit_loop;
}
echo array_pop($arr) . PHP_EOL;
goto start;
exit_loop:
echo "finished" . PHP_EOL;
Aktualizujemy warunek. Sprawdzamy, czy odwrotność warunku nie jest prawdziwa i jeśli tak – wyjście. Wykonujemy akcję i zapętlamy.
Okej, teraz pętla for:
<?php
for($i = 0; $i < 5; $i++){
echo $i . PHP_EOL;
}
Czy umiemy to zapisać z użyciem goto?
Cóż:
<?php
$i = 0;
$incBy = 1;
start:
$cond = $i < 5;
if(!$cond){
goto exit_loop;
}
echo $i . PHP_EOL;
$i = $i + $incBy;
goto start;
exit_loop:
//
Tworzymy zmienną, tworzymy też incBy (o ile zwiększamy po każdej iteracji). Rozpoczynamy, aktualizujemy warunek, sprawdzamy jego przeciwieństwo. Jeżeli jeszcze nie wyszliśmy, to wykonujemy pętlę, zwiększamy o incBy, zapętlamy.
Uwielbiam takie zabawy jako odtrutkę na wysokopoziomowe OOP albo przedawkowanie rekurencji. Pamiętajmy, aby goto generalnie nie używać. Natomiast istnieje taka możliwość i warto ją znać.
Ponadto chęć poznania jak działa algorytm tej czy innej pętli nie powinna wymagać od nas pisania kodu w ASM ani C, jeżeli jesteśmy to w stanie pojąć w bardziej czytelnych warunkach.