この演習では、条件分岐を学びます。値に応じて、2つ以上の行動から1つを選ぶ仕組みです。アナリンの潜入で触れた真偽値と、通貨の換算で扱った整数の計算を土台にしています。
これらはすべてmathにあり、=だけはkernelにあります。
= ( 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 =という判定を短く書けるワードです。数値を消費し、それが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?のように使い、単一の値ではなく範囲で分岐を選ぶことがよくあります。
上の比較ワードは真偽値を返します。その真偽値に基づいて何かをするには、条件を扱うワードに1つ以上のクォーテーションを渡します。クォーテーションとは、角括弧[ ... ]で囲んだコードのひとかたまりです。これを書くと、コードは実行されず、値としてスタックに積まれます。そしてifのようなワードが、どのクォーテーションを実行するかを決めます。
[ neg ] ! a quotation that negates the top of the stack
[ ] ! the empty quotation — does nothing
クォーテーションについては、後の演習で詳しく扱います。今は[ ... ]を「この分岐で実行するコード」と読んでおけば大丈夫です。
if、when、unless
if(kernel)は、真偽値と2つのクォーテーションを受け取ります。真偽値が真なら1つ目のクォーテーションを、偽なら2つ目のクォーテーションを実行します。
if ( ? then-quot else-quot -- )
: abs ( x -- y ) dup 0 < [ neg ] [ ] if ;
whenは真偽値が真のときだけクォーテーションを実行し、unlessは偽のときだけ実行します。
when ( ? quot -- )
unless ( ? quot -- )
if*、when*、unless*
kernelにある3つの派生ワードは、真偽値が真のとき、その値を保持しておく価値のある値として扱います。ワードが「値、または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*は2分岐の形です。真の側の分岐は、値をスタックに残したまま呼び出されます。偽の側の分岐は、値なしで呼び出されます。
42 [ ] [ "nothing" ] if* . ! prints 42
f [ ] [ "nothing" ] if* . ! prints "nothing"
unless*は「値、そうでなければ既定値」という定番の書き方です。値が真ならそのまま使われ、fなら値は捨てられ、代わりにクォーテーションが実行されて代わりの値が積まれます。
"hello" [ "anonymous" ] unless* . ! => "hello"
f [ "anonymous" ] unless* . ! => "anonymous"
when*はif*の1分岐版です。値が真のときだけ、値をスタックに残したままクォーテーションを実行し、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複数の候補から1つを選ぶときには、cond(combinators)がうってつけです。{ predicate body }のペアの配列を受け取り、真になる最初の述語の本体を実行します。
USING: combinators ;
: classify ( n -- label )
{
{ [ dup 0 < ] [ drop "negative" ] }
{ [ dup 0 = ] [ drop "zero" ] }
[ drop "positive" ]
} cond ;
いくつか押さえておきたい点があります。
dup ... <test>がよく使われる書き方です。dropで捨ててから、結果を積みます。この演習では、自動車工場の組み立てラインの生産を分析するコードを書きます。組み立てラインの速度は0(オフ)から10(最大)までの範囲です。
最も低い非ゼロ速度(1)では、1時間に221台の車が生産されます。生産量は速度に比例して増加するので、速度4ではラインは1時間に4 * 221 = 884台の車を生産します。ただし、速度が上がると、不良車が生産される可能性が高くなり、それらは廃棄しなければなりません。
タスクは4つあります。それぞれが1つの整数パラメーター(組み立てラインの速度)をスタックから受け取ります。
速度が0のとき"stopped"を、それ以外の速度のとき"running"を返すようにproduction-statusを定義します。
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を、成功率を考慮した組み立てラインの1時間あたりの生産率を返すように定義します。
まず、定数221であるbase-speedを定義する必要があります。
6 production-rate-per-hour .
! => 1193.4
返される値は浮動小数点数です。
working-items-per-minuteを、1分あたりに何台の正常な車が生産されるかを返すように定義します。結果は整数です(端数の車は数えません)。
6 working-items-per-minute .
! => 19