エンタープライズギークス (Enterprise Geeks)

企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。

JavaScriptでLispインタープリターを作ろう(5) ~変数の導入~

前回の記事では、Lispインタープリターに順次処理「do」を導入した。ここまでの説明で、「順次・反復・分岐」のうち、「順次」と「分岐」は既に導入できたので、残すは「反復」のみとなる。

しかし、反復を説明する前に説明しておくべき事がある。それは「変数」である。このインタープリターにはまだ変数を扱う方法が無い。このため、たとえば反復で必要となるループの脱出条件を記述することが出来ない。そこで今回は、「反復」の説明をする前に「変数」を導入する。

変数の定義を行うdef

変数の定義を可能にするには、インタープリターのevaluate関数に以下の分岐を追加する事で実現できる。

var evaluate = function(x, env) {
  if (Array.isArray(x)) {
    if (x[0] === 'def') { // 変数定義            ← 追加行
      return env[x[1]] = evaluate(x[2], env); // ← 追加行
    } else if (x[0] === 'if') { // 条件分岐
    // ・・・以下略・・・

ifの時と同じように、defというキーワードを特別扱いし、第二引数の計算結果を第一引数の変数名に代入するのである。また、変数に代入した値は、そのままdefの戻り値としている。値を保持する入れ物としては、今まで関数を入れてきたglobalEnvをそのまま利用する。

ソースコード全体は次のようになる。

このdefの利用例を以下に示す。

evaluate(['console.log',
          ['do',
           // 変数「a」作成し、2で初期化する
           ['def', 'a', 2],
           // 変数「b」作成し、1 + 2つまり3で初期化する
           ['def', 'b', ['+', 1, 2]],
           // 変数「a」と変数「b」を足し合わせる
           ['+', 'a', 'b']]],
         globalEnv);

この例ではまず、['def', 'a', 2]という部分で変数aを作成し、この変数a2で初期化している。同様に、['def', 'b', ['+', 1, 2]]という部分で変数bを作成し、1 + 2つまり3で初期化している。最後に、この変数abを足しあわせ、結果をブラウザーJavaScriptコンソールへ出力している。

なお、ここで定義した変数aと変数bのスコープであるが、現在のインタープリターの実装ではglobalEnvに直接値を保持しているため、同一のglobalEnvを使っている間はずっと保持された状態になる。これは一般的なインタープリターで例えると、グローバル変数領域に保持されている状態と同一である。

変数への代入を行うset!

さて、変数への定義が可能になったので、変数への代入を行う操作も追加してみよう。変数への代入操作の名前はset!とする。set!を導入するためには、以下のようにインタープリターのevaluate関数を変更するだけだ。

なお、変数への再代入のような破壊的操作に感嘆符!を付ける文化はSchemeClojureRubyに見られる。このインタープリターでも、このような文化を踏襲した。

var evaluate = function(x, env) {
  if (Array.isArray(x)) {
    if (x[0] === 'def') { // 変数定義
      return env[x[1]] = evaluate(x[2], env);
    } else if (x[0] === 'set!') { // 代入        ← 追加行
      return env[x[1]] = evaluate(x[2], env); // ← 追加行
    } else if (x[0] === 'if') { // 条件分岐
    // ・・・以下略・・・

set!の処理の内容はdefと全く同じだ。defset!違いは「変数定義」と「代入」という役割の違いでしかない。事実、このインタープリターではset!を用いても変数定義は可能であるし、defを用いても代入が可能である。この役割の違いをより明確にし、利便性を向上させるため、本来はset!の処理時に変数がまだ定義されていなかった場合はエラーにした方が好ましいが、このインタープリターではこのようなエラー処理を省いた。

以下にset!導入後のソースコード全体を掲載する。

このdefの利用例を以下に示す。

evaluate(['console.log',
          ['do',
           // 変数「a」作成し、2で初期化する
           ['def', 'a', 2],
           // 変数「b」作成し、3で初期化する
           ['def', 'b', 3],
           // 変数「a」に1を代入する
           ['set!', 'a', 1],
           // 変数「a」と変数「b」を足し合わせる
           ['+', 'a', 'b']]],
         globalEnv);

このコードを実行すると、aの初期値は2だが、あとで代入されて1となり、bに代入されている3と足しあわされて、ブラウザーJavaScriptコンソールへ4が出力される。

さて、これでグローバル変数のみではあるが、変数の作成や代入ができるようになった。次回は反復を導入する。

[近棟 稔]