読者です 読者をやめる 読者になる 読者になる

common lispで文字列処理用の関数を書くときの難点

common lisp 雑記

Javaは文字列(String型)がimmutable。
この仕様は、汎用的な文字列処理関数を書く場合の負担を軽減してくれていると思う。
対してcommon lispの文字列はmutable。
そのため(そのためか?)common lispの文字列処理関数*1は、Javaに比べてインターフェース(?)及び実装が煩雑になる傾向がある、と思っている。
以下、例。

//##########//
//## Java ##//

// 1) 二つの文字列を比較する場合
string1.equals(string2);

// 2) 二つの文字列の部分文字列を比較する場合
// 注目すべきは次の二点。 ※ Javaには詳しくないので間違っている可能性あり
//  a] substringメソッドのおかげで、文字列比較メソッド(equals)自体は、1)の場合と同様に使える
//  b] substringメソッドのコストは、O(1)で大抵は無視できるほど軽い
//      ※ 多分、オリジナルの文字列(char配列)への参照をコピーした後、開始位置と終端位置を設定しなおすだけで済む
string1.substring(0,10).equals(string2.substring(2,12));
;;#################;;
;;## common lisp ##;;

;; 1) 二つの文字列を比較する場合
(string= string1 string2)

;; 2-a) 二つの文字列の部分文字列を比較する場合1 (一番効率が良い方法)
;; Javaとは異なり、文字列比較関数(string=)が、部分文字列を扱うためのオプションを提供している
(string= string1 string2 :start1 0 :end1 10 :start2 2 :end2 12)

;; 2-b) 二つの文字列の部分文字列を比較する場合2 (メモリ消費が大きい方法)
;; 一見、Java版と似ている。
;; ただし、仕様によれば「subseqは常に結果としての新しい列を割り付ける。すなわち、古い列がいる記憶域を決して共有することはない」とのこと。
;; つまり、呼び出しごとに新しい部分列に比例するメモリ領域(+ コピー時間)を消費することになる。
(string= (subseq string1 0 10) (subseq string2 2 12))

;; 2-c) 二つの文字列の部分文字列を比較する場合3 (2-aに比べて非効率。少なくともSBCLでは)
;; 下の方法だと、オリジナルの文字列(string1,string2)を共有することになるので、動作的には一番Javaのそれに近い。
;; ただし、make-array関数にdisplaced-to引数を指定した場合は、結果の文字列(配列)がsimple-stringになることは保証されない。
(string= (make-array 10 :displaced-to string1 :displaced-index-offset 0 :element-type 'character)
         (make-array 10 :displaced-to string2 :displaced-index-offset 2 :element-type 'character))

例にある通り、Javaの場合は、普通の文字列用の関数(メソッド)が、そのまま(substringメソッドと組み合わせることで)部分文字列用の関数(メソッド)としても使用可能となっている。
しかし、common lispの場合、部分文字列も扱えるようにしたい(特に、効率的に扱えるようにしたい)場合は、2-aのように関数側があらかじめ部分文字列用の引数を用意しておく必要がある。
部分文字列も扱える文字列処理関数を自作しようとする場合、これが若干難になると思う。
関数にstartとend引数をつけて実装すること自体は、(多分大抵の場合は)少し気をつけさえすればそれほど難しいことではない。
ただ、「つけ忘れがないように気をつけて、つける場合にはその値のチェック*2もちゃんとして、内部で使用している関連関数にもstartとend引数を追加して、....」などということを(部分文字列も扱いたい)文字列処理関数を書くたびに繰り替えさなければならないのは、個人的には結構面倒臭い。

なので

実装する側からしてみれば、JavaのようにString型用の関数書けば、それが部分文字列に対しても適用できる、というモデルの方が望ましい。
なので、JavaのStringのような型をcommon lispでも作って*3試してみよう、というところで次に続く*4

*1:もっと一般的には配列(or 列)処理関数

*2:startが負になっていないか、endがstartより小さくないか、endが文字列の長さを越えていないか、などなど

*3:既にあるかな?

*4:実際には順序が逆で、そのような型(or パッケージ)を作ってから、これを書いている。ただまだ有用性を試してはいない