這個練習要介紹條件判斷,也就是根據一個值,在兩種或多種做法之間做選擇。它建立在你於 Annalyn 的滲透 認識的布林值,以及 貨幣換算 裡的整數運算之上。
= ( x y -- ? ) ! equal
< ( x y -- ? ) ! less than
<= ( x y -- ? ) ! less than or equal
> ( x y -- ? ) ! greater than
>= ( x y -- ? ) ! greater than or equal
3 3 = . ! => t
2 3 < . ! => t
3 3 <= . ! => t
3 4 = not . ! => t (inequality: combine = with not)
zero?(位於math)是常見的0 =測試的簡寫,它會消耗掉數字,並推入它是否為零:
zero? ( n -- ? ) ! t when n is 0
between?(位於math.order)會測試一個值是否落在包含端點的範圍內。當某個動作取決於數字落在哪個區間時,它就很方便:
between? ( x lo hi -- ? ) ! lo <= x <= hi (inclusive)
5 1 10 between? . ! => t
0 1 10 between? . ! => f
10 1 10 between? . ! => t (inclusive at both ends)
你經常會看到它被當成cond的述詞使用,例如dup 1 4 between?,用來依範圍而不是單一值來挑選分支。
上面的比較字詞會產生布林值;若要對布林值採取行動,你得把一個或多個引述交給條件判斷。引述是一段用方括號[ ... ]包起來的程式碼。寫下它會把程式碼當成值推入堆疊,而不是執行它,接著像if這樣的字詞就會決定要執行哪個引述。
[ neg ] ! a quotation that negates the top of the stack
[ ] ! the empty quotation — does nothing
後面的練習會完整介紹引述;現在,把[ ... ]讀成「這個分支要執行的程式碼」就好。
if、when、unless
if(位於kernel)接受一個布林值和兩個引述。當布林值為真時,它會執行第一個引述;為假時則執行第二個:
if ( ? then-quot else-quot -- )
: abs ( x -- y ) dup 0 < [ neg ] [ ] if ;
when只在布林值為真時執行它的引述;unless則只在為假時執行:
when ( ? quot -- )
unless ( ? quot -- )
if*、when* 和 unless*
三個 kernel 變體在布林值為真時,會把它當成值得保留的值,這在字詞回傳「那個東西,或f」時很有用:
if* ( ? true false -- ) ! truthy: true is called WITH ? on stack
when* ( ? true -- ) ! truthy: true is called WITH ? on stack
unless* ( ? false -- ) ! falsy: false runs and pushes a default
if*是雙分支的形式。真分支被呼叫時,值還留在堆疊上;假分支被呼叫時則沒有:
42 [ ] [ "nothing" ] if* . ! prints 42
f [ ] [ "nothing" ] if* . ! prints "nothing"
unless*是典型的「有值就用值,否則用預設值」慣用語。如果值為真,就原封不動;如果值是f,就把值丟掉,並執行引述來推入替代值:
"hello" [ "anonymous" ] unless* . ! => "hello"
f [ "anonymous" ] unless* . ! => "anonymous"
when*是if*的單分支形式:只有當值為真時,它才會執行引述(值仍留在堆疊上),而當值是f時,就直接把值丟掉。當你想把「某個東西,或f」的結果併入一個累計值,又不想費心處理f的情況時,就可以用它:
0 41 [ + ] when* . ! => 41 (truthy: 41 added to the running total)
0 f [ + ] when* . ! => 0 (falsy: f dropped, total left untouched)
cond當你有好幾個替代動作要從中選擇時,combinators裡的cond是最自然的選擇。它接受一個由{ predicate body }配對組成的陣列,並執行第一個產生真值的述詞所對應的內容:
USING: combinators ;
: classify ( n -- label )
{
{ [ dup 0 < ] [ drop "negative" ] }
{ [ dup 0 = ] [ drop "zero" ] }
[ drop "positive" ]
} cond ;
有幾個細節值得注意:
dup ... <test>是常見的慣用寫法。drop掉輸入,再推入結果。在這個練習中,你會撰寫程式碼來分析汽車工廠裝配線的生產狀況。裝配線的速度範圍從0(關閉)到10(最高)。
在最低的非零速度(1)下,每小時會生產221輛車。產量會隨速度線性增加,所以速度為4時,產線每小時會生產4 * 221 = 884輛車。不過,速度越高,生產出瑕疵車的機率也越高,而這些車之後必須被丟棄。
你有四項任務。每項任務都會從堆疊取出一個整數參數,也就是裝配線的速度。
定義production-status,讓它在速度為0時回傳"stopped",其他速度則回傳"running":
0 production-status .
! => "stopped"
3 production-status .
! => "running"
定義success-rate,回傳產品在生產時沒有發生錯誤的機率:
0:0.0
1到4:1.0
5到8:0.9
9:0.8
10:0.77
10 success-rate .
! => 0.77
定義production-rate-per-hour,回傳裝配線每小時的生產率,並將成功率納入考量。
你需要先定義base-speed,也就是常數221。
6 production-rate-per-hour .
! => 1193.4
回傳的值是浮點數。
定義working-items-per-minute,回傳每分鐘生產多少輛可用的車。結果是整數,未完成的車不列入計算。
6 working-items-per-minute .
! => 19