連載
» 2008年10月31日 00時00分 公開

ちょっと変わったLisp入門Gaucheでメタプログラミング(1)(5/5 ページ)

[吉田裕美,有限会社イーワイオフィス]
前のページへ 1|2|3|4|5       

eval、apply、eval_listの実行経過

 ここまでの説明を、例を使って復習してみます。関数addが(define (add a b) (+ a b))のように定義されています。ここで(add 2 x)を計算します。シンボル「x」の値は3になっています。

 eval、apply、eval_listの実行経過(呼び出しと戻り値)は次のようになります。

  • >から始まる行は関数呼び出し
  • =から始まる行は関数の戻り値
  • -から始まる行は関数内部での処理の解説
> eval((add 2 x), (x.3))
 > apply(add, (2 x), (x.3))
  > eval(add)
   = (lambda (a b) (+ a b))  -- add関数の定義
  > eval_list((2 x), (x.3))
   > eval(2, (x.3))
   = 2  -- 2の値は2
   > eval(x, (x.3))
   = 3  -- 3の値は3
  = (2 3)  -- eval_list((2 x)...)の値
  > eval((+ a b), ((a.2)(b.3)(x.3)))
    > apply(+, (a b), ((a.2)(b.3)(x.3)))
     > eval_list((a b), ((a.2)(b.3)(x.3)))
     > eval(a, ((a.2)(b.3)(x.3)))
     = 2  -- 変数aの値
     > eval(b, ((a.2)(b.3)(x.3)))
     = 3  -- 変数bの値
    = (2 3)  -- eval_list((a b)...)の値
   - (+ 2 3)  -- apply内部でCで書かれた+関数が呼び出される
   = 5  -- apply(+, (a b)...)の値
  = 5  -- eval((+ a b)...)の値
 = 5  -- apply(add...)の値
= 5  -- eval((add 2 x)...)の値

 このように途中経過を手を動かして書いてみると、処理の流れや関数の役割分担が理解しやすいと思います。

スペシャルフォームとマクロ

 最後に、スペシャルフォームとマクロの説明をします。

・スペシャルフォーム

 スペシャルフォームは、apply内部で引数を評価せず、そのままの引数がC言語で書かれた関数に渡されます。ifは次のようになります。

cell if(cell e, cell env) {
  if (eval(CAR(e), env) != NIL) {  // 条件式を評価
    return eval(CAR(CDR(e)), env)  // 真なら2番目の引数を評価
  } else {
    return eval(CAR(CDR(CDR(e))), env)  // 偽なら3番目の引数を評価
  }
}

・マクロ

 マクロはLispの強力な機能の1つです。マクロの動作は、

  1. マクロ定義に引数を評価せず渡す
  2. マクロ定義を評価する
  3. マクロの評価結果を再度評価する

となります。

 PerlやRubyにあるunless文のような、条件が成り立っていなければ、次の式を実行するマクロを定義してみます。

lisp> (defmacro unless(cond exp)
  (list (quote if) cond nil exp))
unless
lisp> (unless (= 1 2)  111)
111
lisp> (unless (= 1 1)  111)
nil

 apply関数の中でマクロは次のようになっています。Lambda式の評価との違いは、引数を評価せず(eval_listを呼び出さず)マクロ定義に渡すことと、マクロ定義の戻り値を再度評価(eval)する点です。

cell apply(cell func, cell args, cell env) {
  cell fbody = eval(func, env);
  cell ftype = CAR(fbody);
  if (ftype == macro_sym) {
    cell params = CAR(CDR(fbody));
    cell e = CAR(CDR(CDR(fbody)));
    cell r = eval(e,
                  concat2(pairlis(params,args)), env));
    return eval(r, env)
  }
}

 先ほどのunlessの例の実行経過を見てみましょう。

> eval((unless (= 1 2)  111), ())
 > apply(unless, ((= 1 2)  111), ())
  > eval(unless, ())
  = (macro (cond exp) (list (quote if) cond nil exp)))
  > eval((list (quote if) cond nil exp), ((cond.(= 1 2)) (exp.111)))
  - 一部省略
  - (list if (= 1 2) nil 111) が評価され
  = (if (= 1 2) nil 111)
  > eval((if (= 1 2) nil 111), ())
  - 一部省略
  - (if nil nil 111), ()) が評価され
 = 111
= 111
 
> eval((unless (= 1 1)  111), ())
 > apply(unless, ((= 1 2)  111), ())
  > eval(unless, ())
  = (macro (cond exp) (list (quote if) cond nil exp)))
  > eval((list (quote if) cond nil exp), ((cond.(= 1 1)) (exp.111)))
  - 一部省略
  - (list if (= 1 1) nil 111) が評価され
  = (if (= 1 1) nil 111)
  > eval((if (= 1 1) nil 111), ())
  - 一部省略
  - (if t nil 111), ()) が評価され
  = nil
 = nil
= nil

 今回は、C言語で書かれたLispの処理系を説明することで、Lispの動作を説明してみました。Lispの処理系が非常にシンプルであることが分かっていただけたらうれしいです。

 今回説明したLispのコードはsvn coで取得できます。また、ソースを見るだけであればこちらのページにアクセスしてみてください。

著者紹介

吉田 裕美

有限会社イーワイオフィス



前のページへ 1|2|3|4|5       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。