今回はGaucheとTUTSchemeの違いで引っ掛かったというわけでは無いのですが、「Scheme入門」を(Gaucheで)読んでいて疑問に思ったこととその解決を記しておきます。
6章関数の演習問題、[6.3.2]です。「任意の個数のリストを引数にして、それらを一つのリストにするmy-append関数を作る」ですね。(解答はP148)
(define (my-append . x)
(letrec [[append2
(lambda (x y)
(if (null? x)
y
(cons (car x) (append2 (cdr x) y))
)
)
]]
(if (null? x)
'()
(append2 (car x) (apply my-append (cdr x)))
)
)
)
(注:括弧及びインデントは私が読みにくいので私が変えて書いてあります)
局所関数ではちょっと分かりにくいので、補助関数を別に作って書き直します。traceして結果が分かりやすいからね!……誰だ? 今、「これだからにわかLisperはさぁ」と言った奴は。
(define (append2 x y)
(if (null? x)
y
(cons (car x) (append2 (cdr x) y))
)
)
(define (my-append . x)
(if (null? x)
'()
(append2 (car x) (apply my-append (cdr x)))
)
)
両方とも再帰を使ってますが、違いがあります。後者は apply を使っています。
で、考えました。
「cdr してくんだからどの道最終的に () [nil] になるんじゃね? apply、要らなくね?」
ね、バカでしょ? さらにバカにも程がある、apply を省いて実行してみました。
(注:やらないほうがいいですよ)
(define (my-append . x)
(if (null? x)
'()
(append2 (car x) (my-append (cdr x)))
)
)
おや? 何の反応も無いぞ? 何も評価結果が表示されない。何でだ……おい、マウスカーソルがカクカクするぞ。
システムモニターを見るとCPUがテンパってました。
「Gauche、暴走を始めました!」
「Emacsごと強制停止しろ」(cv:立木文彦)
「カウントお願いします!」
「3!、2!、1!」
「Emacs強制停止! Gauche、活動を停止しました!」
……何でだよ? 分からん。分からないあまりTwitterで「解せぬ」とぶちぶち愚痴っていますと、@anohana さんが教えてくれました。
子曰く、apply無しに於いてmy-appendへは引数が必ず一つ渡され、(null? x)成立すること能わず、と。
あー、はー、ははぁー。な〜るほ……ど? ん?……んん? いや、再帰でcdrしてcdrし尽すんだから最終的にはmy-appendに渡る引数は絶対 '() になるんじゃないのか、な? な?
そんなこんなで「Scheme入門」を読み返したり禁断(併読ができない性格だから)の「プログラミングGauche」をひもといたりと丸二日ほど考えまくって、ようやく「あ。my-appendの引数は、(my-append . x)と残余パラメータ(「プログラミングGaucheでは「可変長引数」)の形で定義されているから、例えば '(a) を渡すと ((a)) として渡されるわけか。ははーん、は、はぁ〜ん」と、気付きましたと、さ。
つまり
1.(my-deppend '(a) )
とリストを渡すと、
2.(apply my-append (cdr x))
では
3.(apply my-append (cdr ((a)) )
となり
4.(my-append () )
になるわけですが、 my-append の引数はリスト化される(括弧にくるまれる)ので、またもや
5.(apply my-append (cdr x))
は
6.(apply my-append (cdr (()) )
となり cdr しても cdr しても括弧がついて[(())になって]返ってくるという無限ループに陥るわけですな。
はい、そこで apply です。引数をリストで渡すと括弧を剥がして関数に渡してくれるapplyです。なので
(define (my-appned . x)
...)
と定義して
(my-deppend '(a) )
とリストを残余パラメータ(可変長引数)として渡し、((a)) とリスト化されても
(apply my-append (cdr ((a)) )
ならばそれ即ち
(apply my-append () )
となり、そこでようやく
(if (null? x)
...)
に引っ掛かって終了してくれるのですね。
###結!論☆###
残余パラメータ(可変長引数)と apply はセットにして覚えといた方がいいみたいです。
これで納得して先に進んでおけば(そしてユーロ'16がなければ)今頃読み終えられていただろうに、「引数が残余パラメータの関数は、呼び出される都度、その引数がリスト化される」を使って遊んでみようと思ったばかりに姉さん、俺、どつぼにハマりました。
「この括弧を操れるなら……顔文字を作られる関数が出きるんじゃね?」
作りたかった関数は
(f x y)
とすると、「x が y 個の括弧に包まれる」関数です。
まず書いてみたのはこんな。
(define (my-listx x)
(do
((n 0 (+ n 1)))
((= n x))
((lambda x x) 1)))
予想では x 回数、1がリスト化(括弧に包まれ)て
(my-listx 3)
とすると
(((1)))
となる予定、でした。ヘイ! C+x C+e!
gosh> #t
いや……いやいやいや! 真偽を判定して欲しいわけじゃないから!
仕様が無いのでまずは list 関数と同じ関数を作りますか。
(define (my-list . x) x)
然る後に
(define (my-listx x)
(do ((n 0 (+ n 1)))
((= n x)
(my-list 2))))
(my-listx 5)
予想では(((((2))))) となるんじゃないかな?
さてC+x C+e!
gosh> #t
……だからぁ!真偽判定して欲しいわけじゃないってば!
ようし、じゃあGaucheにある関数 構文、while使っちゃうぞ。
(define (my-listx x)
(let ((n 0))
(while (< n x)
(my-list 1)
(set! n (+ n 1)))))
(my-listx 4)
gosh> #t
(define (my-listx x y)
(let ((n 0))
(while (< n y)
(list x)
(set! n (+ n 1)))))
(my-listx 3 4)
gosh> #t
オーケー、オーケー。代入使ってやんよ。この際 set! 使ってやるわ。
(define (my-listx y . x)
(do ((n 0 (+ n 1)))
((= y n) z)
(set! z (my-listx y))))
(my-listx 2 3)
これで (((2))) となるんじゃ……暴☆走しました☆
(define (my-listx . x)
(if (null? x)
()
(my-listx x)))
(my-listx 3)
(これも暴走します)
すんませんでした。取り敢えずあまり考えもせず適当に思いついたコードを評価させてすんませんでした。すんませんでしたから暴走するのは勘弁してください。
その後も
(define (my-listx . x)
(do ((n 0 (+ n 1)))
((= n 10)
(my-listx x))))
(my-listx 3)
(暴走します)
やら
(define (my-listx . x)
(do ((n 0 (+ n 1)))
((= n 10))
(my-listx x)))
(my-listx 3)
(これも暴走するよー)
と暴走コードを書いてはEmacsごと強制停止させるを繰り返し、おとなしく 補助関数を使ってみましたよ。
(define (my-listx x y)
(do
((n 0 (+ n 1)))
((= n y) x)
(set! x (my-list x))))
(my-listx 2 4)
gosh> ((((2))))
こーゆー結果が欲しいの!
補助関数を局所関数にして一つのリストにしてみましょ。
(define (my-listx x y)
(let [[my-list
(lambda z z)]]
(do
((n 0 (+ n 1)))
((= n y) x)
(set! x (my-list x)))))
(my-listx 1 5)
gosh> (((((1)))))
だがしかし、元々は残余パラメータ(可変長引数)が、リスト化されるのを利用したかったわけで。
こういうのはどうだろ?
(define (my-listx y . x)
(do
((n 0 (+ n 1)))
((= n y) x)
(set! x (my-listx x))))
(my-listx 2 1)
gosh> *** ERROR: real number required: (1)
Stack Trace:
_______________________________________
0 (my-listx x)
At line 43 of "(stdin)"
じゃあこういうのは?
(define (my-listx y . x)
(do ((n 0 (+ n 1)))
((= y n) z)
(set! z (my-listx x))))
(my-listx 3 'a)
gosh> *** ERROR: real number required: (a)
Stack Trace:
_______________________________________
0 (my-listx x)
At line 142 of "(stdin)"
エラーコードたちが何をrequireしているのかが分からんのだが。
その後も幾度も
gosh> *** ERROR: real number required
と怒られるも何故怒られているのか、そもそも怒っているのか分からないダメ出しをくらいつつ、もういい! 俺、do ループじゃなくて再帰使うよ! 暴走するなら暴走しろよ! してみろよぉ! と逆ギレして書いてみたのが
(define (my-listx x . y)
(if (zero? x)
y
(my-listx (- x 1) y)
)
)
(my-listx 3 'a)
gosh> ((((a))))
……あ、上手く行ってら。
最後に y が評価されるから x+1 個の括弧が付くけど
・残余パラメータを使い、
・その関数自体を繰り返し(再帰し)て
・set!(代入)を使わず
括弧の数を制御できるリスト作成関数が作成できました。
ようし、これを顔文字にしちゃうぞ。
(define (gakuburu x . y)
(if (zero? x)
y
(gakuburu (- x 1) y)
)
)
(gakuburu 5 ';゚Д゚)
gosh> ((((((;゚Д゚))))))
あっはっは! めっちゃ震えとる! 西野カナばりに震えとる!
どうせなら「ガクブル」と震えさせたいなあ、と思い
(define (gakuburu x . y)
(if (zero? x)
(begin
(display "ガク")
y
(display "ブル")
(gakuburu (- x 1) y)
)
)
)
としてみましたが
(gakuburu 3 ';゚Д゚)
gosh> #<undef>
んー。まあ入出力はまだ先(11章)だからしょうがないか。だいたい私、まだ begin の使い方がまだよく分からないんですよね。
ということで今回はここまで。
参考:twitter.com/anohana/status/737065758052601857
###追記###
@tk_riple さんのコメントを読み、「ほほう、(flush) は良く分からんから取り敢えず else 句に (newline) 入れてみるか」と思ったところ致命的な勘違いをしていたことに気付きましたよ。
……else句が無い。
これじゃそもそも再帰関数になってないじゃないですか。なので書き直し。
(define (gakuburu x . y)
(if (zero? x)
(begin
(display "ガク")
(display y)
(display "ブル")
)
(gakuburu (- x 1) y)
)
)
これで
(gakuburu 2 ';゚Д゚)
gosh> ガク(((;゚Д゚)))ブル#<undef>
と表示されるようになりました。最後の
#<undef>
は依然、謎のままだであるが……それは後に語られる物語であろう。