eq, eql, equal, equalp でハマる。

lispの比較関数でハマりました。
どういうのかって言うとstreamから文字読み出していって文字列にしてハッシュに叩き込む、というやつです。

ちょうどそれ書いてる数時間前にclsqlでテストデータ投入するコード組んでて最初、すごく遅かった(18件/秒くらい)んですが declare やらメモリの再利用やらしてやるととたんに数百倍に速度アップしてくれた後でした。

まぁ、最近始めたばっかりのlisper一年生の身としてはガベコレ遅いのねー、くらいに思って次のコードを書き始めたわけですが・・・

で、何が起こったかというとハッシュがおかしい。
文字列をキーにしてたんですが、違う文字列でgethashしてもなんかキーがマッチしてしまう・・・

で、試しに以下のサンプルコードで実験してみると・・・

(let ((a (make-array 2 :element-type 'character))
      (h (make-hash-table :test 'equal)))
  (progn
    (setf (aref a 0) #\a
	  (aref a 1) #\a)
    (setf (gethash a h) 'a)
    (setf (aref a 0) #\b)
    (gethash a h)))

結果、帰ってきた戻り値は・・・
=> A, T

・・・字面がちがうのにマッチする・・・
微かにeqとかはシンボルマッチ都下に使われる用でメモリアドレスしか見ないから文字列マッチには使ったらダメだよー、都頭にこびりついてたのでequalでつくってたんですが・・・Hyperspecちゃんと読めってことなんでしょうねぇ。

http://www.lispworks.com/documentation/HyperSpec/Body/f_equal.htmによると・・・

equal is true of two objects if they are symbols that are eq, if they are numbers that are eql, or if they are characters that are eql. 

とのことで先にeqで比較してからeqlするよー、とのこと。
つまり読み込んだ文字列をバッファに溜め込むようなやり方をすると同じアドレス上に文字列が作られるのでハッシュでは比較に使えない、ということらしいです。

で、まぁどういう風に回避したかっつーと・・・
字面ちがうと違う、同じなら同じっていう判定してほしかったので文字列をinternしてシンボルにしてeqで比較することでにげましたとさ。