LoginSignup
0
1

More than 5 years have passed since last update.

Schemeの“ポインタ”のようなもの、或いは『Scheme入門』をGaucheで読む(1.5)

Posted at

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 部へ代入 なのだろうと思われます。
こんな感じなんじゃないでしょうか?

リスト.jpg

この、「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> (())

となります。今の q はこんな感じですな。
queue1.jpg

それから

(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) です。この状態をコンスセルで表すと
queue2.jpg
このように、同一の (a) が q の car部 cdr部にポイントされていて、それらがそれぞれの (a) では無いことを示すために
gosh> (#0=(a) . #0#)
と記されるようです。
その後順次

(enqueue! q 'b)
(enqueue! q 'c)

とやっていくと
queue3.jpg
そーれから♪
queue4.jpg

と、 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も活動を止めました!」
とやる気まんまんだったので、ちょっと残念でした。

0
1
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1