SchemeプログラマがCommon Lispでハマりそうなところ - lisp-cookbook-ja/common-lisp GitHub Wiki
SchemeとCommon Lispではっきりと違うところは、それ程問題にならないかと思いますが、名前が似ているのに結果や引数が微妙に違ったりするところはハマりどころです。
:CLではstringはvectorのsubtypeである:
(vectorp "foo")
;=> T
(vector? "foo")
;=> #f
こういうことをしているとはまります
(cond ((vectorp obj)...)
((stringp obj)...)
...)
:3.141592と書いた場合single-floatになる: R5RS Schemeでは基本double-floatですが、CLでは、デフォルトでは、single-floatとして読み取られます。
double-floatで読む込むにはread-default-float-formatに、double-floatを設定します。もしくは、3.141592d0と書き直します。 :1.と書いた場合、1.0の略記ではない: CLは、10進表記の1ということになります。read-baseが10以外の時などに利用したり 10進であることを強調する場合に用いられます。
:&rest引数が必ずしもコピーされない: Schemeでは、"""(define (foo . xs) ...)""" と定義して """(apply foo ys)""" と呼び出した場合、xsは常に新たにアロケートされたリスト(ysはコピーされる)であることが保証されています (R5RS 4.1.4)。CLでは"""(defun foo (&rest xs) ...)""" と定義して """(apply #'foo ys)"""と呼び出した場合に、処理系はysをそのまま(コピーせずに)xsに渡して良いことになっています (CLHS 3.4.1.3)。コピーする処理系もありますが、ポータブルなコードでは&rest引数を破壊する場合は自分でコピーしなければなりません。
&rest引数を破壊するなんて普通しないと思うかもしれませんが、delete-ifとか sortなどをうっかり使ってしまう、ってことはあります(実話)。
:doマクロは繰り返し変数を上書きする: Schemeではdoマクロは再帰に展開されるので、繰り返し変数は繰り返しの度に新たに作られます。繰り返しが進んでも、一度クロージャで取り込んだ値は変わりません。
(do ((x 0 (+ x 1))
(r '() (lambda () x)))
((= x 1) (r))
)
;=> 0 ;; (lambda () x)が閉じ込めるxはx=0の時のxで、x=1になるxとは別
CLではこうなります。
(do ((x 0 (+ x 1))
(r '() (lambda () x)))
((= x 1) (funcall r))
)
;=> 1 ;; (lambda () x)が閉じ込めるxが繰り返しによって上書きされx=1になっている
繰り返し変数のその時の値を捕捉したい場合は、"""(let ((x x)) (lambda () x))"""などとする必要があります。
:map系の名前でも返り値を利用しないものがある: Schemeでは、map系の名前は、要素に関数を適用して、新しい集合を作って返すもの、for-each系の名前は、mapに似ているけれども、副作用が目的で、返り値は利用しないもの、という命名規約がありますが、CLでは、mapから始まっている名前だからといって返り値を利用するとは限りません。
mapから始まる名前で、返り値を利用しないことが慣習である(もしくは返り値が有用でない)関数には、
:listpは、list?と動作が違う: CLのlistpは、consか、nilであれば、真正リストかどうかは関係なくTを返します。
(listp '(car . cdr))
;=> T
(list? '(car . cdr))
;=> #f
:シンボルのreadはpackageの影響を受ける: S式でデータをセーブ/ロードしたりネットワーク越しに送ったりするのはLisperの常套手段ですが、 Common LispではS式を読み込む時にシンボルが「その時点でのpackage」にinternされます。 全く同じに見えるS式を読んでいてもpackageが異なると読んだものが同じになりません。 Schemeでは名前空間はシンボルとは別の層で管理されてて、シンボル自体は同じに見えればeq?なので、 送ってるデータをダンプしていくら睨んでも原因がわからず途方に暮れることが。