Gauche:組み込み関数の再定義

Gauche:組み込み関数の再定義


発端 - car, cdrを再定義したら…

今、car/cdr を lisp の様に空リストに対して適用可能にしたとしよう。

gosh> (define (car lis)
        (if (null? lis)
            ()
            (car lis)))
gosh> (car ())
()
gosh> (car '(3))
3
gosh> (define (cdr lis)
        (if (null? lis)
            ()
            (cdr lis)))
cdr
gosh> (cdr ())
()
gosh> (cdr '(1 2 3))
(2 3)

しかし、これ書き下ろしている最中に間違っていると思ってて、 うまく動作することの方に驚いた。 事実、上記の car/cdr を atama/sippo で行うと無限ループに落ちる。

また、 guile では car/cdr でもやはり無限ループに落ちる。 おそらく (define (foo args) body) とあって、 foo のスロットを作る前に body 実体を作ってから foo に束縛するか foo のスロットを作ってから body 実体を作るかの差かなとも思うのだが如何 ?

ちなみに以下が確実なものと考える。

gosh> (define (atama lis)
         (if (null? lis)
             ()
             (car lis)))
atama
gosh> (atama ())
()
gosh> (atama '(3))
3
gosh> (define (sippo lis)
         (if (null? lis)
             ()
             (cdr lis)))
sippo
gosh> (sippo ())
()
gosh> (sippo '(1 2 3))
(2 3)

理由

Shiro: あっほんとだ。これはGaucheの方の問題と考えるべきです。 body実体を作るタイミングというか、正確にはbodyがコンパイルされるタイミング の問題です。Gaucheはコンパイル時にいくつかの組み込み関数が 再定義されていないかどうかを調べ、再定義されていない場合は インライン展開するのですが、ご指摘の通りcarが再束縛される前に 本体がコンパイルされるので、本体内のcarは組み込みのcarとして インライン展開されてしまいます。インライン展開しない組み込み関数なら、 再定義すればbody内でも再定義した関数が参照されます。

組み込み関数を再定義する場合は、別名で定義しといて 最後に(define car my-car)とかするのがイディオムですね。

Gaucheに限って言えば、再定義した組み込み関数の中でオリジナルの 組み込み関数を参照するには次のような手があります。

(define car
  (let ((orig-car (with-module gauche car)))
    (lambda (lis)
      (if (null? lis)
          '()
          (orig-car lis)))))

別解

これじゃいかんの?

(define car
  (let ((orig-car car))
    (lambda (lis)
      (if (null? lis)
          '()
          (orig-car lis)))))

私もこれでいいのかなぁって思いました。 で、ここで逆に本当に組み込み関数についてはこれでよいか?という疑問がふつふつと。

Shiro: これもよく使われるイディオムなんですが、インタラクティブな 開発において、こう書いてあるファイルを何度もloadして使ってると orig-carの参照が多段になっていくのが個人的にちょっと。動くのは動くんで いいんですが。

でもなあ…

組み込み関数の再定義については gosh では、上記の様な使い方をするようにという仕様(上記でShiroさんがイディオムと表現していることを指してます。誤解してたらごめんなさい。)でもいいのですが、ユーザとしては組み込みか否かはあまり意識したくないような感じ。 ある意味予約された語彙は予約されてない語彙と区別して使わないとユーザの意図した通りにはならないわけですよね。

まぁ大体組み込まれているものは認知されてて(実際私も知った上でやったわけだし)、あまり再定義みたいなことって現実問題としてはやらないのかもしれませんけど、なんとなく気になったりします。cut-sea:2004/01/12 18:06:31 PST

問題の根っ子

Shiro: 最も基本的な関数でさえ再定義できることが Scheme/Lispのいいところだと思っているので、あまり「組み込み関数だから」 特別扱いはしたくないですね。

今回の再帰定義に関していえば、問題は「組み込み関数だから」 ではありません。問題の本質は次の2点にあります。

これらの問題を回避しようとすると、インタラクティブな定義は あきらめて、replでは評価のみ、定義は常にファイルから読む、というような 方向しかないんじゃないか、という気がします。

Generic関数を使ったら?

(事実上)再定義する時にgeneric関数を使うと楽かな...と思って試みたのですが

(define-method + (a b) "Hi")
(+ 1 2) ==>"Hi" (methodが適用されている)
(+ 1 2 3) ==>6 (素の手続きが適用されている)

+は通るんですけど

(define-method log (a b) "Hi")
(log 1 2) ==> "Hi"
(log 1) ==> *** ERROR: no applicable method for #<generic log (1)> with arguments (1)

logだと通りません。 generic関数が素の手続きを遮蔽する/しないって決まっていないんですか? もちろん、明示的に

(define-method log (z) ((with-module gauche log) z))

を追加すればエラーは消えるんですが、簡便さのためということで。。。

Shiro (2005/04/15 17:57:06 PDT): これはautoloadとdefine-methodの絡んだバグですね… logはgoshの起動直後はautoloadされる関数として登録されているんですが、 define-methodが同名のgeneric functionを探す際にautoloadを見つけられないので 新たにlogというgeneric funcitonを作って束縛をシャドウしてしまうのです。 define-methodの前に一回でもlogを使っていれば予想通り動作します。

この問題はGauche:GenericFunctionとModuleとも絡むので、 すっきりした解決法が欲しいところです。


Last modified : 2012/02/07 07:59:30 UTC