APPLY 検討

使用例で挙げたのは以下なナニ。

gosh> (disasm (lambda () (apply (lambda (x) x) 1)))
main_code (name=#f, code=0x80d7b40, size=6, const=1, stack=1):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x) x)
     2 PUSH 
     3 CONSTI(1) 
     4 APPLY(2)                 ; (apply (lambda (x) x) 1)
     5 RET 
internal_closure_0 (name=#f, code=0x81359c8, size=2, const=0 stack=0):
args: #f
     0 LREF0                    ; x
     1 RET 
#<undef>
gosh> 

引数のパースが微妙、って思ってたんですが引数が一つだったら楽できる (pop しなくて良い) 作りになってるように見える。
例えば引数が二つ以上だと

gosh> (disasm (lambda () (apply (lambda (x y) (+ x y)) 1 2)))
main_code (name=#f, code=0x80f6d20, size=7, const=1, stack=2):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x y) (+ x y))
     2 PUSH 
     3 CONSTI-PUSH(1) 
     4 CONSTI(2) 
     5 APPLY(3)                 ; (apply (lambda (x y) (+ x y)) 1 2)
     6 RET 
internal_closure_0 (name=#f, code=0x81f1d00, size=4, const=0 stack=1):
args: #f
     0 LREF1-PUSH               ; x
     1 LREF0                    ; y
     2 NUMADD2                  ; (+ x y)
     3 RET 
#<undef>
gosh> 

なカンジで push が一発入っている。引数と手続きを取り出しているのは以下の部分。

                int nargs = SCM_VM_INSN_ARG(code);
                ScmObj cp;
                while (--nargs > 1) {
                    POP_ARG(cp);
                    VAL0 = Scm_Cons(cp, VAL0);
                }
                cp = VAL0;     /* now cp has arg list */
                POP_ARG(VAL0); /* get proc */

nargs が 1 ならループの中には入らない。
む、ちょっとタンマ。引数一つの場合 (使用例のケース) だと唯一の引数 1 が val0 に格納されてて、それがそのまま cp に、という形??
だとすると Scm_VMApply 手続きの中の処理と整合しないんだけどなぁ。

ダウト

成程。APPLY の引数は (apply 手続きオブジェクト 引数 ....) になってるとゆー事は、引数が一つだけ、なのは

(apply (lambda () 1))

みたいなケースなのか。disasm してみるとどうなるか。

gosh> (disasm (lambda () (apply (lambda () 1)))) 
*** ERROR: Compile Error: wrong number of arguments: apply requires 2, but got 1
"(stdin)":4:(disasm (lambda () (apply (lambda () ...

Stack Trace:
_______________________________________
gosh> (disasm (lambda () (apply (lambda () 1) '())))
main_code (name=#f, code=0x80d73a8, size=6, const=1, stack=1):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda () 1)
     2 PUSH 
     3 CONSTN 
     4 APPLY(2)                 ; (apply (lambda () 1) '())
     5 RET 
internal_closure_0 (name=#f, code=0x814a640, size=2, const=0 stack=0):
args: #f
     0 CONSTI(1) 
     1 RET 
#<undef>
gosh> 

なるほど。nargs が 1 ってのはあり得んのかな。でもなんかまだヒッカカる。

                while (--nargs > 1) {

ってコトは nargs が 2 ならループの中には入らない、はず。てーコトはやっぱ直上のケースだと '() が cp にセットされて pop された # な手続きオブジェクトが val0 に格納される算段になるな。
ええと、この場合は numargs は 0 になるんで問題無いと思われるのですが

(apply (lambda (x) x) 1)

の場合は?

ナチュラル君

とほほ。上記なソレはあり得ませんでした。

gosh> (apply (lambda (x) x) 1) 
*** ERROR: improper list not allowed: 1
Stack Trace:
_______________________________________
  0  (lambda (x) x)
        At line 8 of "(stdin)"
gosh> 

やれやれ。apply に渡す手続きオブジェクトの引数はリストですか。

gosh> (apply (lambda (x) x) '(1))
1
gosh> (apply (lambda (x y) (+ x y)) '(1 2))
3
gosh> 

うーむ。いかんなぁ。使用例は以下にすべき。

gosh> (disasm (lambda () (apply (lambda (x) x) '(1))))
main_code (name=#f, code=0x80f6be0, size=7, const=2, stack=1):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x) x)
     2 PUSH 
     3 CONST (1)
     5 APPLY(2)                 ; (apply (lambda (x) x) '(1))
     6 RET 
internal_closure_0 (name=#f, code=0x814a498, size=2, const=0 stack=0):
args: #f
     0 LREF0                    ; x
     1 RET 
#<undef>
gosh> 

だとすると、上記のケースでは

  • cp に '(1) が格納
  • val0 に # が格納

という形になりますな。なんというカン違い。こうなってるのであれば Scm_VMApply 手続きもさくっと追えます。やれやれ。
とは言え、そうなったらそうなったで

APPLY (3)

みたいなケースでどんな形なんでしょ、というのが微妙に気になります。これはどちらかというと scheme の仕様の世界ッスか??

仕様

以下、一部を引用 (R5RS です)

(apply proc arg1 ... args) プロシージャ
proc にはプロシージャ、args にはリストを指定する。リスト (append (list arg1 ...) args) の要素を実引数に用いて proc を呼び出す。

とあるな。while なループの部分はマサにこれですな。試しに確認。

gosh> (disasm (lambda () (apply (lambda (x y z) (+ x y z)) 1 2 '(3))))
main_code (name=#f, code=0x80f8e10, size=9, const=2, stack=3):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x y z) (+ x y z))
     2 PUSH 
     3 CONSTI-PUSH(1) 
     4 CONSTI-PUSH(2) 
     5 CONST (3)
     7 APPLY(4)                 ; (apply (lambda (x y z) (+ x y z)) 1 2 '( ...
     8 RET 
internal_closure_0 (name=#f, code=0x80f6b40, size=7, const=0 stack=1):
args: #f
     0 LREF2-PUSH               ; x
     1 LREF1                    ; y
     2 NUMADD2                  ; (+ x y z)
     3 PUSH 
     4 LREF0                    ; z
     5 NUMADD2                  ; (+ x y z)
     6 RET 
#<undef>
gosh> (apply (lambda (x y z) (+ x y z)) 1 2 '(3)))
6
gosh>

ええと、ループな部分が以下。

                while (--nargs > 1) {
                    POP_ARG(cp);
                    VAL0 = Scm_Cons(cp, VAL0);
                }
                cp = VAL0;     /* now cp has arg list */

nargs は 4 です。正味 2 回繰り返しの中が実行される。

  • 最初に 2 が pop されて cp に格納
    • val0 は '(2 3) になる
  • 次に 1 が pop されて cp に格納
    • val0 は '(1 2 3) になる

という事で成程ねー、という事ですな。

もう少し

                TAIL_CALL_INSTRUCTION();

が何故に必要か、というあたりが微妙だったのですが、

gosh> (disasm (lambda () (cons (apply (lambda (x y z) (+ x y z)) 1 2 '(3)) '())))
main_code (name=#f, code=0x80f7d80, size=12, const=2, stack=3):
args: #f
     0 CLOSURE #<lambda 0>      ; (lambda (x y z) (+ x y z))
     2 PUSH 
     3 CONSTI-PUSH(1) 
     4 CONSTI-PUSH(2) 
     5 CONST (3)
     7 APPLY(4)                 ; (apply (lambda (x y z) (+ x y z)) 1 2 '( ...
     8 PUSH 
     9 CONSTN 
    10 CONS                     ; (cons (apply (lambda (x y z) (+ x y z))  ...
    11 RET 

みたいなソレだと、APPLY (というか Scm_VMApply 手続き) で PC が書き変わる。使用例なソレだと APPLY の次の instruction は RET になってるので手続きな RET で終了、で構わないのですが、上記のケースだと継続フレームに以降の処理を保管しておいて、APLLY な手続きでの RET で取り出す、というワケですか。

いやはや

これで一応まとめが書けるレベルになっているはず。