2009年9月3日木曜日

schemeメモ - quoteで作ったリスト

単純に次ぎのコード、
(define (foo) '(a b c))
(foo) 
  ==> (a b c)
fooはどってことのない関数だが、実は、この書き方はとても危ない。

プログラムのどこかで、fooの出力を破壊的に変更してしまうと、
(reverse! (foo)) 
  ==> (c b a)
(foo) 
  ==> (a)
もはや、関数fooは (a b c)を返さなくなる。

vectorも同じだ。
(define (foo) '#(a b c))
(vector-set! (foo) 0 'aa)
(foo)
  ==> #(aa b c)

準クォートを使ったこれもだめだ
(define (foo x) `(,x a b c))
(foo 1)
  ==> (1 a b c)
(reverse! (foo 2))
  ==> (c b a 2)
(foo 1)
  ==> (1 a 2) 

しかしこれは大丈夫 (少なくともgoshでは)
(define (foo x) `(a b c ,x))
(foo 1)
  ==> (a b c 1)
(reverse! (foo 2))
  ==> (2 c b a)
(foo 1)
  ==> (a b c 1) 

無難な書き方はこうかな?
(define (foo x) (append `(,x a b c) '()))
(foo 1)
  ==> (1 a b c )
(reverse! (foo 2))
  ==> (c b a 2)
(foo 1)
  ==> (1 a b c )

まとめ
  • クォートで作ったリストやベクターは、静的にアロケートされている可能性がある
  • クォートで作ったリストやベクターに対し、直接に破壊操作を行わない。
  • 知らない関数の出力を破壊する時は、 前もってコピーしたものを破壊するのが無難。

しかしこれに慣れると、他の言語で同様なことを書くのに躊躇してしまいそうだ。
例えば、rubyだと
def foo ; return [1,2,3] ; end
foo.reverse!
  => [3, 2, 1]
foo
  => [1, 2, 3]
で全然問題ないのに、
def foo ; return [1,2,3].clone ; end 
と書いてしまうとか......

0 件のコメント:

コメントを投稿