トラック
/
Factor
Factor
/
演習
/
ラザニアの達人
ラザニアの達人

ラザニアの達人

学習演習

はじめに

短いワードなら、スタックを操作するやり方でも問題ありません。ワードが複数の入力を持ち、それらを名前で参照したくなったら、Factorにはローカル変数があります。これは::(ワード定義用)や[| | ](インラインのラムダ用)で導入する、名前付きの束縛です。

:: ( inputs -- outputs )

:を::に置き換えると、入力の名前が本体の中のローカル変数になります。

USING: locals ;

:: hypotenuse ( a b -- c )
    a a *
    b b *
    + sqrt ;

スタックを操作するバージョンと比べてみましょう。

! DOCTEST: SKIP   (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
    [ sq ] bi@ + sqrt ;

どちらも有効です。ローカル変数が力を発揮するのは、同じ入力を2回以上参照するときや、演算の順序がスタックの順序と自然に一致しないときです。

:>で中間の値を束縛する

::の本体(またはインラインの[| ... |]ラムダ)の中では、:>がデータスタックから値を1つ取り出し、新しいローカル変数に束縛します。

value :> name      ! immutable binding
value :> name!     ! mutable binding (name! is its setter)

よく使うのはイミュータブルなほうです。中間の値に名前を付けて、本体を上から下へ読めるようにしましょう。

USING: locals math ;

:: average-3 ( x y z -- avg )
    x y + z + :> sum
    sum 3 / ;

!を付けたほうは、ローカル変数をミュータブルとして宣言します。本体の後半でvalue name!(コロンも:>も付けません)と書くと、その値を再代入できます。

USING: kernel locals math math.order ;

:: clamped ( x lo hi -- y )
    x :> v!
    v lo < [ lo v! ] when
    v hi > [ hi v! ] when
    v ;

ミュータブルなローカル変数は、後の演習で出てくる繰り返しのワード(whileなど)の中でこそ力を発揮します。

[letでどこからでもスコープを作る

:>は字句スコープの中でしか使えませんが、::と[| ... |]はそのスコープを自動で作ります。ふつうの:ワードやリスナーからローカル変数を導入したいときは、コードを[let ... ]で包みます。

[let code :> name code :> name ... body ]

各:> nameは、その前の式のスタックの一番上の値を束縛します。この束縛は、フォームの残りの部分から参照できます。

USING: locals math ;

[let 3 :> x 4 :> y x x * y y * + ] .   ! => 25

[letは、スタックスタイルの:定義と名前付き束縛の橋渡しをしてくれます。ワードの一部分だけがローカル変数の恩恵を受けるようなときに便利です。

ラムダ:[| inputs | body ]

クォーテーションの中では、[| inputs | body ]がローカル変数を導入し、本体から名前で参照できるようにします。これはassoc-mapのような高階ワードで特に役立ちます。assoc-mapが受け取るクォーテーションには、keyとvalueが渡されるからです。

USING: assocs locals ;

H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }

同じ形はmap-index([sequences][sequences]にあります)でも使えます。map-indexが受け取るクォーテーションには、要素とそのインデックスが渡されます。名前を付けておくと、どのオペランドがどんな役割を担うのかがはっきりします。

USING: locals sequences ;

! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }

ラムダのスタック効果は、入力のリストと、本体が何を生成するかによって決まります。

ローカル変数を使うべきとき

次のようなときは、ローカル変数を使いましょう。

  • 同じ入力を複数の場所で使うとき。
  • 演算の自然な順序がスタックの順序と一致せず、それを合わせるためのdup/swap/rotが読みにくいとき。
  • assoc-mapのように、複数の値を一度に渡してくる高階ワードに渡すインラインのラムダの中にいるとき。

本体がきれいな直線的な流れになっているなら、ふつうの:定義のほうが短く、少なくとも同じくらい読みやすくなります。

assoc-map:エントリーを1つずつ

assoc-map(assocsにあります)は、連想コレクションの各エントリーにクォーテーションを適用します。

assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )

そのクォーテーションを書くには、ローカル変数を使ったラムダがもっともすっきりした方法です。

condをもう一度

fまたは数値によって処理を分岐させたいときは、condがまさにうってつけです。比較する単一の値がないので、caseは必要ありません。

説明

キッチンに戻って、リアの絶品ラザニアのレシピをさらに磨き上げましょう。ここでの課題では、複数の入力を一度にさばくことになります。まさにそういうときにこそ、ローカル(Factorの名前付き束縛)の出番です。

1. 調理の状態を判定する

cooking-statusを定義して、スタックからタイマーの読み取り値を取り出し、状態を表す文字列を返すようにします。

  • 0 → "Lasagna is done."
  • f(タイマーが設定されていない) → "You forgot to set the timer."
  • それ以外の数値 → "Not done, please wait."
12 cooking-status .   ! => "Not done, please wait."
0 cooking-status .    ! => "Lasagna is done."
f cooking-status .    ! => "You forgot to set the timer."

2. 準備時間を見積もる

preparation-timeを定義して、層の名前の配列と、1層あたりの平均分数を受け取り、準備時間の合計を返すようにします。

{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18

3. 必要な麺とソースの量を計算する

quantitiesを定義して、層の名前の配列を受け取り、2つの値を返すようにします。必要な麺のグラム数(麺の層1つにつき50g)と、必要なソースのリットル数(ソースの層1つにつき1/5リットル)です。

{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5

4. 秘密の材料を加える

友達が材料の配列を送ってきます。最後の項目が、その友達の秘密の材料です。add-secret-ingredientを定義して、友達の配列と自分の配列を受け取り、自分の配列の末尾に秘密の材料を加えたものを返すようにします。

{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }

5. レシピの分量を調整する

料理本のレシピは2人前できます。scale-recipeを定義して、レシピ(材料名から分量へのハッシュテーブル)と目標の人数分を受け取り、それに合わせて分量を調整した新しいレシピを返すようにします。

H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Factor Exercism

ラザニアの達人を始める準備はできましたか?

Exercismに登録すれば、47個のコンセプト163個の演習、そして本物の人間によるメンタリングとともに、Factorを学んでマスターできます。すべて無料です。