Common Lisp で Perl 風に (join “Separator” @list) と文字列の結合を気軽にできる関数(若しくはマクロ)を用意してみよう。
まずは、@list の部分にアトムのリストを与える感じで関数を書いてみる。
やり方は色いろあるだろうけど、concatenate 関数を使って、
;; 使用例: (join " " '("cat" "file-name" "|" "fgrep" "'lisp'")) ;; -> "cat file-name | fgrep 'lisp'" ;; 呼び出される関数 (defun join (sep str-list) (cond ((null str-list) "") (t (join-str sep str-list "")))) ;; 実際の仕事をする補助関数 ;; result 引数につなげた文字列を溜め込んでいく (defun join-str (sep str-list result) (cond ((null str-list) result) (t (join-str sep (cdr str-list) (cond ((string= result "") (car str-list)) (t (concatenate 'string result sep (car str-list))))))))
補助関数 join-str のこの部分、
(cond ((string= result "") (car str-list)) (t (concatenate 'string result sep (car str-list))))
は、最後のアトムのあとに sep 文字列が付け足されないようにするための条件分岐になっている。
さて、上のやり方でもいいのだけど、format 関数の繰り返し処理を使うと、(暗号みたいにはなってしまうのだけど)もっと簡単に書ける。
;; join by format がリストを受け取るバージョン ;; (format nil "~{~A~^ ~}" '("cat" "file-name" "|" "fgrep" "'lisp'")) ;; -> cat file-name | fgrep 'lisp' (defun joinl (sep str-list) (format nil (concatenate 'string "~{~A~^" sep "~}") str-list))
format 関数を使った joinl 関数は、最初の join 関数と等価だ。
format 関数は ~(チルダ)の後に来る文字を制御文字(メタ文字)として扱う。
- ~{ と ~} で囲まれた部分は繰り返し処理になる。
- ~A は format 関数が受け取った文字列をはめ込む。
- ~^ は format 関数がリストの最後の文字列を受け取った時点で繰り返し処理を抜ける(それ以降の処理を無視する)ように制御するためのメタ文字。
- sep の前に ~^ が置かれているので、最後の文字列を受け取った後は、sep は全体の文字列の最後に付加されずに format 処理が終了する。
もう一つの例を紹介しよう。
join* に与える引数は殆どの場合リストでいいだろうけど、単に文字列のアトムをずらずら連ねたほうが便利な場合もある。
これを join@ というマクロで定義してみる。
;; (format nil "~@{~A~^ ~}" "cat" "file-name" "|" "fgrep" "'lisp'") ;; -> cat file-name | fgrep 'lisp' (defmacro join@ (sep &rest strs) `(format nil (concatenate 'string "~@{~A~^" ,sep "~}") ,@strs))
format 関数は繰り返し制御のはじめで ~@{ というメタ文字を与えられると、受け取るアトムの連なりをリストとして解釈してくれる。
マクロにしたのはなぜかというと、これを単純に関数で定義すると、
(defun join@-fn (sep &rest strs) (format nil (concatenate 'string "~@{~A~^" sep "~}") strs)) ;; (join@-fn " " "cat" "file-name" "|" "fgrep" "'lisp'") ;; -> "(cat file-name | fgrep 'lisp')"
と、リスト状のものが文字列で返ってきてしまうから。
format 関数はその他にもいろいろ裏技がたくさんあるので、引き続きいろいろ調べるつもり。
参考:
What’s the canonical way to join strings in a list? – stackoverflow