前言
控制結構 (control structure) 或控制流程 (control flow) 用來改變程式運行的方向。可分為兩大類:
- 選擇 (selection)
- 迭代 (iteration)
本文會介紹 Raku 中常見的控制結構。
選擇相關的控制結構
if .. elsif .. else
if
是最常見的選擇控制結構。實例如下:
my $n = 0;
if $n > 0 {
say "n is larger than zero";
} elsif $n < 0 {
say "n is smaller than zero";
} else {
say "n is equal to zero";
}
if
也可以用於後位修飾敘述,如下例:
say "Equal" if 1 + 2 == 3;
後位修飾算是 Perl 家族語言的特色之一,對於寫 one liner 很方便。
unless
unless
和 if
語義相反,條件不符合時才會執行程式碼,因此較少使用。
die "Some error" unless 1.0 + 2.0 - 3.0 == 0.0;
unless
沒有相對應的 elsif
和 else
區塊。如果覺得 unless
不好用,將條件寫成同義的 if
即可。
with .. orwith .. else
with
可檢查條件是否有定義,算是一種 if
的語法糖。實例如下:
my $food = "seafood";
with $food.index("hamburger") {
"{$food} is delicious.".say;
} orwith $food.index("chip") {
"I like {$food} as well.".say;
} else {
"No favored food".say;
}
without
without
是反向的 with
,有點像 unless
對於 if
的關係。實例如下:
# Declare a variable without defining it.
my $answer;
warn "Undefined" without $answer;
同樣地,without
沒有 orwith
及 else
區塊。
given .. when .. default
given
算是另一種簡化 if
的語法,類似 C 語言的 switch
。實例如下:
my $w = Date.today.day-of-week;
given $w {
when 3 {
"Hump day".say;
}
when 5 {
"Thank God It's Friday".say;
}
when 6..7 {
"Weekend".say;
}
default {
"Week".say;
}
}
proceed 和 succeed
proceed
和 succeed
可以改變 given
區塊的流程。proceed
會繼續前進到下一個 when
區塊,而 succeed
則會跳出整個 given
區塊。以下是實例:
my $w = Date.today.day-of-week;
given $w {
when 1 {
proceed;
}
when 2 {
proceed;
}
when 4 {
"Week".say;
}
when 3 {
"Hump day".say;
}
when 5 {
"Thank God It's Friday".say;
}
when 6 {
proceed;
}
when 7 {
"Weekend".say;
}
default {
die "Unknown day";
}
}
迭代相關的控制結構
一般的程式設計中,將這類控制結構稱為迴圈 (loop)。
while
while
在符合條件的情形下,會無限次執行特定程式,如以下實例:
my $i = 1;
while $i <= 10 {
"Counting to {$i}".say;
$i++
}
until
until
是反向的 while
,會無限次執行特定程式,直到符合某條件才停止,如以下實例:
my $i = 1;
until $i > 10 {
"Counting to {$i}".say;
$i++
}
如果覺得 until
寫起來不直覺,改成相對應的 while
即可。
repeat .. while 或 repeat .. until
加上 repeat
敘述後,即使條件不合,該區塊至少會執行一次。其他用法則和 while
或 until
相同。
my $i = 10;
repeat {
"Counting to {$i}".say;
$i++
} until $i > 0;
loop
loop
相當於傳統的 C 風格迴圈。實例如下:
loop (my $i = 0; $i < 10; $i += 2) {
$i.say;
}
若 loop
不加上任何計數器,會變無窮迴圈 (infinite loop),如下例:
loop {
"Hello".say;
}
for
for
搭配迭代器 (iterator) 使用。透過迭代器,不需了解容器內部實作,而可以直接走訪該容器。使用 ...
生成一個 Range 物件,可作為迭代器。如以下實例:
for 1...10 {
"Counting to {$_}".say;
}
在後續的文章中,我們會將 for
搭配陣列 (array) 或雜湊 (hash) 使用。
改變迴圈行進
next
next
可以跳過目前的迴圈,進入下一次迭代。見以下實例:
loop (my $i = 1; $i <= 10; $i++) {
if $i % 2 != 0 {
next;
}
$i.say;
}
last
last
會直接離開目前的迴圈。見以下實例:
my $i = 1;
loop {
if $i > 5 {
last;
}
$i.say;
$i++;
}
redo
redo
會重新開始同一次迭代。見以下實例:
loop {
my $x = prompt("Enter a number: ");
redo unless $x ~~ /\d+/;
last;
}
用標籤跳躍多層迴圈
next
、last
、redo
都可以搭配標籤 (labels),可以跳離多層迴圈。
OUTAHERE: while True {
for 1,2,3 -> $n {
last OUTAHERE if $n == 2;
}
}
實例:終極密碼
這裡用終極密碼這個經典的題目來展示如何使用控制結構。我們先在 1 至 100 間隨機挑出一個數字,再讓玩家猜這個數字。範例如下:
constant $MIN = 1;
constant $MAX = 100;
# Get a random answer between $MIN and $MAX.
my $answer = ($MIN..$MAX).pick;
# Initial program state.
my $guess = -1;
my $upper = $MAX;
my $lower = $MIN;
loop {
# Get the guess from user.
loop {
my $input = prompt("Guess a number between {$lower} and {$upper}: ");
# Check input is a valid number.
unless $input ~~ m/ \d+ / {
$*ERR.say: "Invalid number";
redo;
}
# Check
unless $lower <= $input and $input <= $upper {
$*ERR.say: "Invalid range";
redo;
}
# Update the guess.
$guess = $input;
last;
}
# Check whether the guess is right.
if $guess == $answer {
say "You got it";
last;
} elsif $guess > $answer {
say "Too large";
$upper = $guess;
} else {
say "Too small";
$lower = $guess;
}
}