set-car!/set-cdr! で思ったように行かなかったので色々試行錯誤した結果、分かったことを記しておきます。
環境はEmacsでGauche。
(set-car! '(2 2 3) 1)
で
(1 2 3)
と評価されるだろうな、と思っていたら
gosh> #<undef>
となって、どうすればいいんだろ? と試行錯誤したところ
(define x '(2 2 3))
とリストを定義してからから
(set-car! x 1)
とするとやはり
gosh> #<undef>
と評価されますが、x自体を評価しますと
(1 2 3)
と評価されます。
これはおそらく、set-car!/set-cdr! が単にリストの 先頭/その残り を入れ替えるのではなく、(この場合)xの指すコンスセルの car/cdr 部へ代入 なのだろうと思われます。
こんな感じなんじゃないでしょうか?
この、「xの指すコンスセルの car/cdr 部へ代入」てさー……
**所謂、ポインタだよね。**C言語でいうところの。
……うん、知ってた。ポインタの無いプログラム言語なんて無いって、知ってた。
ともかく、Schemeにおける set-car!/set-cdr! がポインタ(矢印)なのだろうと言うことがよく分かる例に、queue を挙げます。参考例は
「Scheme 入門10. 代入」
の
4.1. 待ち行列 (Queue) の実装
より。
ところでこのページののっけから「代入(むやみに)使うべからず」のように注意されてますし、最後にも「関数型で代入はあんまし使わない」というようなことが書かれてますよね。
さらに上述した例、
(define x '(2 2 3))
(set-car! x 1)
のような用法に至っては「プログラミングGauche」(Kahuaプロジェクト (著), 川合 史朗 (監修, 監修) 2008年 オライリージャパン)P121のコラムで
「本当はやってはいけない」
とまで書かれてます。
いやはや、関数型におけるset!一族のこの嫌われようと言ったら。一体何やらかしたんだ、set!一族。関数型言語業界のお偉いさんの娘にでも手を出したのか。
そんなset!系ですが、取り敢えず教科書に載ってる以上使ってみないわけにもいかず、使う以上、使い方も知らないわけにいかんのです。きっと若気の至りだったんです、許してあげてください、関数型言語業界のお偉いさん。
さて「4.1. 待ち行列 (Queue) の実装」のうち、make-queue と enqueue! だけGaucheでやってみました。コードは紹介した
「Scheme 入門10. 代入」
で見るなりコピペするなりしてください。
それでは括弧打ち開始。
(define q (make-queue))
で q というqueueを作ります。この時点で q を評価すると
gosh> (())
それから
(enqueue! q 'a)
(enqueue! q 'b)
(enqueue! q 'c)
とやっていきますと評価するつど
gosh> (a)
gosh> (a b)
gosh> (a b c)
と評価されます。
ところがq 自体を評価しますと
gosh> ((a b . #0=(c)) . #0#)
……何だこの謎のリストは。
うーん、と悩みに悩み(本当はそこまで悩まず)ググったところ、いらっしゃるものですね、同じような箇所で嵌った方が。はい、こちらです。公式(?(と言うか作者の川合史朗氏のサイト))へのリンク
も貼ってあります。ありがたや。
#0=(c)
は、(c)に #0 というラベルをつけて、同じオブジェクトを #0# で表しているようです。つまり
((a b . #0=(c)) . #0#)
は
((a b . (c)) . (c))
つまり
((a b c) c)
で、 enqueue!関数 の最後で queue を car しているので ((a b c) c) の car である(a b c)が返されるんですな。
試しに
(enqueue! q 'a)
の時点で q を評価すると
gosh> (#0=(a) . #0#)
と返ってきます。つまり
((a) . (a)) すなわち ((a) a) です。この状態をコンスセルで表すと
このように、同一の (a) が q の car部 cdr部にポイントされていて、それらがそれぞれの (a) では無いことを示すために
gosh> (#0=(a) . #0#)
と記されるようです。
その後順次
(enqueue! q 'b)
(enqueue! q 'c)
と、 q の cdr部をポイントするコンスセルが次々と最後のコンスセル(この場合は (a) から (b) 、(b) から (c) )に変わっていき、そしてそれらのコンスセルは q のcar部にポインタされた最初のコンスセル(この場合は (a) )から次々につなげられて(ポインタされて)いくので、q をcar すると (a b c) が返ってくるわけですね……うーん、よくもまあ、こんなアクロバティックなコード、というか技法を思いつくものだなぁ。
なので q 自体を評価すると car部に (a b c) が、 cdr部に (c) がポイントされていて、そしてこの (c) はcar部の (a b c) の最後のコンスセルである (c) と同一であることを示すため、単に
((a b c) c) {もしくは((a b . (c)) . (c))}
ではなく
((a b . #0=(c)) . #0#)
と記されるようです。(ちょっとこんがらがってきた)
では湯浅太一著『Sheme入門』(岩波書店 1991年)の96ページ、巡回リストをやってみましょう。
(define x '(eat rice))
(set-cdr! x x)
さて x を評価しますと……
gosh> #0=(eat . #0#)
と返ってきました。これはコンスセル x の cdr部にコンスセル x 自体が入っている、ということですね。リストにすると
(eat . (eat . (eat . (eat . ……(事象の地平の彼方で) (eat . (eat))))……(事象の地平の彼方で)))
で、結果
(eat eat eat eat eat eat ……(事象の地平の彼方で) eat)
となります。
同書には「こういうことが起こるから強制終了の仕方を調べてやるように」ということが書かれていますので私は
「Gauche暴走開始! Emacsごと強制終了させます! カウントよろしく!」
「3! 2! 1!」
「Emacs可動停止! Gaucheも活動を止めました!」
とやる気まんまんだったので、ちょっと残念でした。