「48時間でSchemeを書こう/評価: 第一部」の版間の差分

削除された内容 追加された内容
120 行
== 基本的なプリミティブを加える ==
 
次に、私たちのSchemeを簡単な計算機として使えるようにします。まだ「プログラミング言語」と呼ぶには程遠いですが、前進はしています。
Next, we'll improve our Scheme so we can use it as a simple calculator. It's still not yet a "programming language", but it's getting close.
 
Begin by adding a clause to eval to handle function application. Remember that all clauses of a function definition must be placed together and are evaluated in textual order, so this should go after the other eval clauses:
 
<code>eval</code>に関数適用を扱う節を加えることから始めましょう。覚えておかねばならないのは、関数定義の全ての節はまとめておかなければならず、書かれた順に評価されるということです。従って、次の節は他の節全ての後に置かれるべきです。
<syntaxhighlight lang="haskell">
eval (List (Atom func : args)) = apply func $ map eval args
</syntaxhighlight>
 
これも入れ子になったパターンですが、今回はリテラルのリストではなくコンス演算子<code>:</code>に対してマッチします。Haskellにおいて、リストは実際のところ数珠繋がりになったコンス適用と空リストの構文糖衣です: <code>[1, 2, 3, 4] = 1:(2:(3:(4:[])))</code>。リテラルのリストではなくコンス自体にパターンマッチさせることで、私たちは「リストの二番目の要素」の代わりに「リストの残り」を指定していることになります。例えば、<code>(+ 2 2)</code>を<code>eval</code>に渡したとすると、<code>func</code>は<code>+</code>に束縛され、<code>args</code>は<code>[Number 2, Number 2]</code>に束縛されます。
This is another nested pattern, but this time we match against the cons operator ":" instead of a literal list. Lists in Haskell are really syntactic sugar for a chain of cons applications and the empty list: <span class="inline_lisp">[1, 2, 3, 4] = 1:(2:(3:(4:[])))</span>. By pattern-matching against cons itself instead of a literal list, we're saying "give me the rest of the list" instead of "give me the second element of the list". For example, if we passed <span class="inline_lisp">(+ 2 2)</span> to eval, <span class="inline_code">func</span> would be bound to "+" and <span class="inline_code">args</span> would be bound to [Number 2, Number 2].
 
節の残りは2つの既出の関数とまだ定義していない関数1つで構成されています。再帰的に引数のそれぞれを評価するために<code>eval</code>を<code>args</code>にmapします。これによって私たちは<code>(+ 2 (- 3 1) (* 5 4))</code>のような組み合わさった式を書くことができます。その後、評価済の引数のリストをもとの関数と共に<code>apply</code>に渡します。
The rest of the clause consists of a couple of functions we've seen before and one we haven't defined yet. We have to recursively evaluate each argument, so we map <span class="inline_code">eval</span> over the args. This is what lets us write compound expressions like <span class="inline_lisp">(+ 2 (- 3 1) (* 5 4))</span>. Then we take the resulting list of evaluated arguments, and pass it and the original function to apply:
 
<syntaxhighlight lang="haskell">
apply :: String -&gt;> [LispVal] -&gt;> LispVal
apply func args = maybe (Bool False) ($ args) $ lookup func primitives
</syntaxhighlight>
 
The built-in function 組み込み関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vlookup lookup] looks up a key はペアのリストからキー(its first argumentその最初の引数) in a list of pairs. However, を探します。しかし、リスト中のどのペアもキーにマッチしない場合、<code>lookup will fail if no pair in the list contains the matching key. To express this, it returns an instance of the built-in type </code>は失敗するでしょう。これを表現するため、<code>lookup</code>は組み込みの[http://www.haskell.org/onlinereport/standard-prelude.html#$tMaybe Maybe]. We use the function 型のインスタンスを返します。[http://www.haskell.org/onlinereport/standard-prelude.html#$vmaybe maybe] to specify what to do in case of either success or failure. If the function isn't found, we return a 関数を使って成功・失敗の時にそれぞれどうするかを指定します。もし関数が見つからなければ、<code>#f</code>に相当する<code>Bool False value, equivalent to #f </code>型を返します(we'll add more robust error-checking later後でより頑健なエラーチェックを入れます). If it ''is'' found, we apply it to the arguments using 。もし見つかれば、<code>($ args), an operator section of the function application operator.</code>を使ってそれを引数に適用します。
 
次に、私たちの実装するプリミティブのリストを定義します。
Next, we define the list of primitives that we support:
 
<syntaxhighlight lang="haskell">
primitives :: [(String, [LispVal] -&gt;> LispVal)]
primitives = [("+", numericBinop (+)),
("-", numericBinop (-)),
151 ⟶ 152行目:
</syntaxhighlight>
 
<code>primitives</code>の型を見てください。<code>primitives</code>は<code>lookup</code>に期待されるようにペアのリストですが、''ペアの値は<code>[LispVal]</code>から<code>LispVal</code>への関数です''。Haskellでは、簡単に関数を他のデータ構造に格納することができます。ただし、格納される関数は全部同じ型を持っている必要があります。
Look at the type of <span class="inline_code">primitives</span>. It is a list of pairs, just like <span class="inline_code">lookup</span> expects, ''but the values of the pairs are functions from [LispVal] to LispVal''. In Haskell, you can easily store functions in other data structures, though the functions must all have the same type.
 
また、格納されている関数自身、関数<code>numericBinop</code>(まだ定義されてませんが)の結果です。<code>numericBinop</code>はHaskellのプリミティブ関数を取り、引数のリストを解すコードでラップし、プリミティブ関数を適用し、結果を<code>Number</code>コンストラクタでラップして返します。
Also, the functions that we store are themselves the result of a function, <span class="inline_code">numericBinop</span>, which we haven't defined yet. This takes a primitive Haskell function (often an operator section) and wraps it with code to unpack an argument list, apply the function to it, and wrap the result up in our <span class="inline_code">Number</span> constructor.
 
<syntaxhighlight lang="haskell">
numericBinop :: (Integer -&gt;> Integer -&gt;> Integer) -&gt;> [LispVal] -&gt;> LispVal
numericBinop op params = Number $ foldl1 op $ map unpackNum params
 
unpackNum :: LispVal -&gt;> Integer
unpackNum (Number n) = n
unpackNum (String n) = let parsed = reads n in
if null parsed
then 0
else fst $ parsed !! 0
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0
</syntaxhighlight>
 
As with R5RS Scheme, we don't limit ourselves to only two arguments. Our numeric operations can work on a list of any length, so と同様、2引数だけに囚われないことにします。私たちの定義する数値操作はどんな長さのリストにも働くことができます。よって<code>(+ 2 3 4)</code> = 2 + 3 + 4, and で、<code>(- 151 5 5 3 2)</code> = 15 - 5 - 3 - 2. We use the built-in function です。ビルトイン関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vfoldl1 foldl1] to do this. It essentially changes every cons operator in the list to the binary function we supply, を使ってこうします。<span class="inline_code"code>foldl1</code>は、本質的には、リスト中の全てのコンス演算子を二項演算子<code>op</spancode>.に置きかえます。
 
R5RS Schemeとは違って、私たちは''弱い型付け''の一種を実装しています。それは、もし値が数値として解釈できる(文字列"2"のように)ならば、それが文字列であっても数値として扱うということを意味します。これは<code>unpackNum</code>に幾つか余分の節を付けることで実現します。もし私たちが文字列をunpackしているならば、Haskellのビルトイン関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vreads reads]でパースできるか試してみます。<code>reads</code>は(パースされた結果の値、元の値)というペアのリストを返します。
Unlike R5RS Scheme, we're implementing a form of ''weak typing''. That means that if a value can be interpreted as a number (like the string "2"), we'll use it as one, even if it's tagged as a string. We do this by adding a couple extra clauses to unpackNum. If we're unpacking a string, attempt to parse it with Haskell's built-in [http://www.haskell.org/onlinereport/standard-prelude.html#$vreads reads] function, which returns a list of pairs of (parsed value, original value).
 
リストに対しては、一つの値のみを持つリストに対してパターンマッチを行い、それをunpackすることを試みます。他は全て次の場合に流れつきます。
For lists, we pattern-match against the one-element list and try to unpack that. Anything else falls through to the next case.
 
どんな理由にしろ数値を得ることができなかった場合、今のところは0を返すことにしておきます。すぐ後にこれがエラーを出すようにします。
If we can't parse the number, for any reason, we'll return 0 for now. We'll fix this shortly so that it signals an error.
 
通常通りコンパイル・実行してください。関数の引数それぞれに対して<code>eval</code>を呼ぶことで、いかに私たちが入れ子の表現を「タダで」使うことができるかに注目しましょう。
Compile and run this the normal way. Note how we get nested expressions "for free" because we call eval on each of the arguments of a function:
 
<syntaxhighlight lang="text">