2018年1月31日水曜日

リスト - Alexandria

概要

このページでは、Common Lispの汎用ユーティリティであるAlexandriaの「リスト」構造に関するオペレータを紹介します。

alist-plist関数: 連想リストを属性リストに変換

Alexandriaのalist-plist関数は名前の通り連想リストを属性リストに変換します。
(alexandria:alist-plist '((a . 1) (b . 2) (c . 3)))
; => (A 1 B 2 C 3)

plist-alist関数: 属性リストを連想リストに変換

plist-alist関数は逆に属性リストを連想リストに変換します。
(alexandria:plist-alist '(a 1 b 2 c 3))
; => ((A . 1) (B . 2) (C . 3))

assoc-valueアクセッサ: setfマクロに対応したassoc

ANSI Common Lispではassocという連想リストへの参照を行う関数が定められています(第14章「リスト」)。この関数は連想リストからkeyを手がかりに(key . value)のコンス(エントリー)を取得するものです。

Alexandriaのassoc-value関数はANSI標準のassocに対して2つの拡張が施されています。

  • 返り値は多値であり、1番目の返り値はvalueのみを、2番目の返り値がエントリーを返します。
  • setfマクロにも対応しているため、値の変更が簡単にできます。
特に2つ目の拡張は連想リストを使う場合には大変便利です。以下に例を示します。

;; ALIST の準備
(defparameter *alist* nil)
; => *ALIST*

;; エントリーの追加
(setf *alist* (acons 'a 1 *alist*))
; => ((A . 1))

;; ANSI標準の assoc でもアクセスは容易にできる
(assoc 'a *alist*)
; => (A . 1)

;; Alexandriaの assoc-value の返り値は多値
(alexandria:assoc-value *alist* 'a)
; => 1 ;
;    (A . 1)

;; setf で変更ができるため便利
(setf (alexandria:assoc-value *alist* 'a) 2)
; => 2
*alist*
; => ((A . 2))

rassoc-valueアクセッサ: valueベースのassoc-value

ANSI Common Lispにはassocと同様にrassoc関数が定められており、keyではなくvalueを基にエントリーを参照することができます。Alexandriaのrassoc-valueアクセッサもvalueを基に参照します。上の例に続いて、以下のサンプルを実行してみてください。
(alexandria:rassoc-value *alist* '2)
; => A ;
;    (A . 2)

doplistマクロ: 属性リストに対する繰り返し

属性リストはkeyvalueが交互に現れる普通のリストであるため、繰り返しの処理は比較的用意ですが、Alexandriaのdoplistを使うと最も簡潔かつ明瞭に属性リストに対する繰り返しを行うことができます。
(defparameter *plist* '(a 1 b 2 c 3))
; => *PLIST*

(alexandria:doplist (k v *plist*) (format t "~a: ~a~%" k v))
; A: 1
; B: 2
; C: 3
; => NIL

各種モディファイマクロ

AlexandriaではANSI Common Lisp標準の関数に対してモディファイマクロが追加されています。モディファイマクロとは、変数の変更を伴うことができるオペレータです。

Macro: appendf
append関数のモディファイマクロ
Macro: nconcf
nconc関数のモディファイマクロ
Macro: unionf
union関数のモディファイマクロ
Macro: nunionf
nunion関数のモディファイマクロ
Macro: reversef
reverse関数のモディファイマクロ
Macro: nreversef
nreverse関数のモディファイマクロ

例えばappendnconcの違いはリストをコピーしてから連結するか、破壊的に修正して連結するかの違いで、それぞれの返り値を使うことが前提であることに変わりはありません。つまり、引数を再利用するか、という点が破壊的な修正を行うべきかの判断基準になります。unionnunionreversenreverseなどの関係も同様です。(参考:第14章「リスト」)

他方、Alexandriaが提供するのはモディファイマクロなので、返り値を使うことを前提とするのではなく、変数の束縛を変更することを前提に使用されます。つまり、appendnconcで得た返り値をsetfsetqで束縛するなら、代わりにAlexandriaのappendfnconcfを使った方が美しく書ける、ということです。

以下にサンプルを示します。
(defparameter *list* '(1 2))
; => *LIST*

;; ANSIの append は変数の束縛状況を変更しない
(append *list* '(3 4))
; => (1 2 3 4)

;; 変わっていない
*list*
; => (1 2)

;; Alexandriaの appendf は変数の束縛状況を変更できる
(alexandria:appendf *list* '(3 4))
; => (1 2 3 4)

;; 変更されている
*list*
; => (1 2 3 4)
ちなみに、union関数は標準仕様入門では説明していませんが、リストを集合とみなす関数です。
(union '(a b c) '(d c e))
; => (A B D C E)

circular-list関数: 循環リストの生成

Alexandriaのcircular-list関数は手軽に循環リストを生成するオペレータです。循環リストとは、リストの一番最後の参照が一番最初に繋がっているデータ構造で、循環構造を本質的に表すことができます。動作(コード)では「無限ループ」と呼ばれるものがありますが、循環リストは「無限データ」です。

循環リストを扱うときはあらかじめ*print-circle*スペシャル変数をtに変更しておく必要があります。これをしないと通常のリストのように表示しようとして、無限ループに陥ります。

Alexandriaのcircular-list関数で循環リストを生成し、ANSI標準のpopマクロで値を取り出す例を示します。循環リストは本質的に無限なので、popで値を取り出しても終わりはありません。

;; 必ずスペシャル変数を変更すること!
(setq *print-circle* t)
; => T

;; Alexandriaの circular-list 関数で循環リストを生成する
(defparameter *circle* (alexandria:circular-list 1 2 3))
; => *CIRCLE*

;; 循環リストの表記は以下の通り
*circle*
; => #1=(1 2 3 . #1#)

;; pop し続けても終わらない
(pop *circle*)
; => 1
(pop *circle*)
; => 2
(pop *circle*)
; => 3
(pop *circle*)
; => 1
(pop *circle*)
; => 2
(pop *circle*)
; => 3

make-circular-list関数: 初期値による循環リストの生成

Alexandriaのcircular-list関数は引数から循環リストを生成しますが、make-circular-list関数は要素数と初期値から循環リストを生成します。
(setq *print-circle* t)
; => T

(alexandria:make-circular-list 3 :initial-element 0)
; => #1=(0 0 0 . #1#)

circular-list-p関数: 循環リストの判定

circular-list-p関数は循環リストであるかどうかを判定します。上の例に続けて、以下のサンプルを試してみてください。
(alexandria:circular-list-p *circle*)
; => T

proper-list-p関数: プロパーリストの判定

Alexandriaのproper-list-p関数はproper listであるかどうかを判定します。プロパーリストとは、最後のセルのCDR部がnilになっているリストです。
;; 一般的なリスト表記によるプロパーリスト
(alexandria:proper-list-p '(1 2 3))
; => T

;; 最後のコンスのCDR部がnilではないインプロパーリスト
(alexandria:proper-list-p '(1 2 . 3))
; => NIL

;; プロパーリストのコンス表記
(alexandria:proper-list-p '(1 . (2 . (3 . nil))))
; => T

;; インプロパーリストのコンス表記
(alexandria:proper-list-p '(1 . (2 . (3 . 4))))
; => NIL

;; nilはプロパーリスト
(alexandria:proper-list-p nil)
; => T

ensure-car関数: CAR部またはオブジェクトそのものの取得

ensure-car関数は引数がリスト(コンス)の場合はCAR部を、そうでないなら引数そのものを返すオペレータです。ANSI標準のcar関数は引数がリストでない場合にエラーを通知しますが、ensure-carはエラーを通知しないという違いがあります。
(alexandria:ensure-car 1)
; => 1

(alexandria:ensure-car '(1 2))
; => 1

ensure-cons関数: コンスの取得

ensure-cons関数は引数がリスト(コンス)の場合は引数そのものを、そうでないならCDR部をnilとするコンスを返します。ANSI標準のcons関数は引数が2つですが、ensure-consは引数が1つです。
(alexandria:ensure-cons 1)
; => (1)

(alexandria:ensure-cons '(1 2))
; => (1 2)

ensure-list関数: リストの取得

ensure-list関数がensure-cons関数と違うのは、nilに対する動作だけです。

ANSI Common Lispではcons型はnilを含まず、list型はnilを含むと定められています(第14章「リスト」)。そのため、ensure-cons関数でnilを引数に渡すとコンスではないと判断され、'(nil . nil)というコンスが、つまり'(nil)が生成されます。他方、ensure-list関数でnilを引数に渡すとリストであると判断されるため、引数であるnilがそのまま帰ってきます。
(alexandria:ensure-cons nil)
; => (NIL)

(alexandria:ensure-list nil)
; => NIL

一般に説明文の中でリストやコンスという言葉を使うときは明示的な区別をせず使いますが、型としてのconslistは包含関係になっていますので注意してください。

remove-from-plist関数: 属性リストからの削除

属性リストからkeyを手がかりにエントリーを削除するにはremove-from-plist関数を使います。
(alexandria:remove-from-plist '(a 1 b 2 c 3) 'b 'c)
; => (A 1)

第2引数以降は可変長ですので、いくつでも指定して削除することができます。

delete-from-plist関数: 属性リストからの破壊的な削除

delete-from-plist関数は前節のremove-from-plist関数と似た動作をしますが、引数を破壊的に修正する点が異なります。
(defparameter *plist* '(a 1 b 2 c 3))
; => *PLIST*

(alexandria:delete-from-plist *plist* 'b)
; => (A 1 C 3)

*plist*
; => (A 1 C 3)

勘違いしやすい点ですが、破壊的な動作を伴う関数をモディファイマクロのように使用してはいけません。破壊的な修正は一般に引数を再利用しない場合に使用するもので、引数の変更はあくまで副次的な過程で発生する「副作用」です。このような関数は破壊的ない(コピーを行う)関数と同様に返り値を使用することが大前提です。

remove-from-plistf, delete-from-plistfマクロ: モディファイマクロ

変数に束縛された属性リストからエントリーを削除し、改めてその変数に束縛し直すためのモディファイマクロは関数とは別に定められています。使い方は同じですが、こちらはモディファイマクロなので、引数を返り値で束縛し直すという点が特徴的です。remove系とdelete系の違いは引数をコピーするかどうかです。

mappend関数: appendを用いるmapcan

ANSI Common Lispにはmapcanという高階関数が定義されており、リストの各要素に第1引数の関数を適用し、返り値のリストをnconc関数で連結するというオペレータです。第14章「リスト」にサンプルを掲載していますので、参照してください。

Alexandriaのmappend関数はリストの連結にappendを用いる点が異なります。下の方で紹介するmap-product関数の実装に使用されています。。

(alexandria:mappend #'identity '((1 2)(3 4)(5 6)))
; => (1 2 3 4 5 6)

(alexandria:mappend #'cdr '((1 2)(3 4)(5 6)))
; => (2 4 6)

(alexandria:mappend #'(lambda (v) `(,(car v))) '((1 2)(3 4)(5 6)))
; => (1 3 5)

setp関数: 重複のないリストであるかの判定

Alexandriaのsetp関数は、引数のリストの要素に重複がないかどうかを判定する述語関数です。リストでない場合や重複がある場合にnilを返し、重複のないリストである場合にtを返します。なお、nilに対しては空集合としてtを返します。

以下にサンプルを示します。
(alexandria:setp '(a b c))
; => T

(alexandria:setp '(a b a))
; => NIL

(alexandria:setp nil)
; => T

set-equal関数: 2つのリストの要素が同じであるかの判定

set-equal関数はリストを2つ引数に取ります。そして、両方のリストに含まれる要素が同一であればtを、そうでなければnilを返します。

片方の要素が別のリストに含まれない場合はnilとなりますが、重複は許容するため、要素数が異なるだけでnilになることはありません。以下のサンプルで確認してください。
(alexandria:set-equal '(a b c) '(b c a))
; => T

;; 同じ要素を持つなら要素数が異なっていても t となる
(alexandria:set-equal '(a b c) '(a b c a))
; => T

(alexandria:set-equal '(a b c) '(a b c d))
; => NIL

map-product関数: 直線的なマッピング

ANSI Common Lispの標準仕様ではmapcarという定番の高階関数が定められています。この関数は引数のリストの各要素に対して並列的に関数を適用し、結果として一つのリストを返します。

下の例を見れば分かるように、'aを取り出す時には同時に'cを、'bを取り出すときには同時に'dを取り出します。
(mapcar #'list '(a b) '(c d))
; => ((A C) (B D))

Alexandriaのmap-product関数は組み合わせを考えるアルゴリズムに近く、1つの要素を固定して、残りの全ての要素との組み合わせを取りながら関数を適用します。
(alexandria:map-product #'list '(a b) '(c d))
; => ((A C) (A D) (B C) (B D))

上の例では、'aを取り出す時には'cのみならず'dも取り出され、'bを取り出す時にも同様に'c'bが取り出されているのが分かります。

productという言葉から行列やベクトルの積を思い浮かべるかもしれませんが、必ずしも計算とは無関係です。例えば、リストを数学上のベクトルと見なし、手前のリストを行ベクトル、2つ目のリストを列ベクトルと見なした場合、ANSI Common Lispのreducemapを使うだけで簡単に実装できます。

(defun vector-product (x y) (reduce #'+ (map 'vector #'* x y)))
; => VECTOR-PRODUCT

;; (1 * 3) + (2 * 4) = 11
(vector-product '(1 2) '(3 4))
; => 11

;; リスト構造でもベクトル構造でも使える
(vector-product '(1 2) #(3 4))
; => 11

flatten関数: 複雑なリスト構造の単純化

再帰の練習などでよく見かける「フラット化」のための関数もAlexandriaには用意されています。flattenです。
(alexandria:flatten '((1 (2 3)) (4 (5 (6 7 8) 9) 10)))
; => (1 2 3 4 5 6 7 8 9 10)

関数 - Alexandria

概要
このページではCommon Lispの汎用ユーティリティであるAlexandriaの関数に関するオペレータを紹介します。

ensure-function関数: 関数でも関数指定子でも関数実体を返す

高階関数で関数を引数として渡すとき、クォート(')だけで渡しても、シャープクォート(#')で渡しても、多くの場合は適切に処理されます。しかし、実際にはクォートだけならそれはquoteスペシャルオペレータが使われるためシンボルが返され、シャープクォートならfunctionスペシャルオペレータが使われるため関数の実体が返されます。

このような細かい動作の違いを解消するため、関数の実体を確実に取り出すことができるのがAlexandriaのensure-function関数です。
(alexandria:ensure-function '+)
; => #<SYSTEM-FUNCTION +>

(alexandria:ensure-function #'+)
; => #<SYSTEM-FUNCTION +>

disjoin関数: 述語関数をorでつなぐ

Common Lispには「述語」と呼ばれる関数があり、条件判定を行うために用いられます。Alexandriaのdisjoin関数は、複数の述語関数を引数に取り、それらの述語のいずれかがtを返す場合にtを返すような「述語関数の合成」を行います。

例えば、「ゼロまたはプラス」という条件であれば以下のように記述できます。
;; 書き方サンプル1: 事実をそのまま記述する
(mapcar #'(lambda (x) (<= 0 x)) '(-1 0 1))
; => (NIL T T)

;; 書き方サンプル2: 手動で述語関数を組み合わせる
(mapcar #'(lambda (x) (or (zerop x) (plusp x))) '(-1 0 1))
; => (NIL T T)

;; 書き方サンプル3: disjoin高階関数を使う
(mapcar (alexandria:disjoin #'zerop #'plusp) '(-1 0 1))
; => (NIL T T)

合成すべき述語関数が多くなればdisjoinが便利です。

conjoin関数: 述語関数をandでつなぐ

Alexandriaのconjoin関数はdisjoinの逆で、述語関数をandで繋ぎます。
(mapcar (alexandria:conjoin #'integerp #'plusp) '(-1 0 1 1.0))
; => (NIL NIL T NIL)

compose関数: 関数を合成する

disjoinconjoinは述語関数の合成ですが、数学的な関数合成を行うのがcomposeです。

例えば、2乗を行うdouble関数を定義し、さらにANSI標準の1を加算する1+関数を合成するには以下のようにします。
(defun double (x) (expt x 2))
; => DOUBLE

(funcall (alexandria:compose #'double #'1+) 10)
; => 121
結果を見れば分かる通りこれは f(x)=x2 と g(x)=x+1 を合成した f(g(x)) = (x+1)2 です。右の関数が左の関数に合成されていき、最終的には一つの関数になります。

curry関数: 関数と引数をlambdaで包む

ある種の計算を行う際は、「カリー化」を使うと手軽で便利なことがあります。

例えば、年を表す数列'(15 16 17)があるとします。これは2000年分が省略されているので、全ての値に2000を足せば完全な年を表すことができます。
(mapcar (lambda (x) (+ 2000 x)) '(15 16 17))
; => (2015 2016 2017)

Alexandriaのcurry関数は上の例におけるlambdaを合成します。
(mapcar (alexandria:curry #'+ 2000) '(15 16 17))
; => (2015 2016 2017)

rcurry関数: 引数の順序を逆にしたcurry

rcurrycurryの違いは引数を適用する順序です。以下の例を見てください。
;; curry は (lambda (x) (- 10 x)) となる
(mapcar (alexandria:curry #'- 10) '(1 2 3))
; => (9 8 7)

;; rcurry は (lambda (x) (- x 10)) となる
(mapcar (alexandria:rcurry #'- 10) '(1 2 3))
; => (-9 -8 -7)

named-lambdaマクロ: 名前付のlambda

lambdaは「無名関数」のため、普通は「再帰」を書くことができません。再帰関数を定義する場合はdefunlabelsを使います。

Alexandriaのnamed-lambdalabelsを使ってlambdaを再実装したもので、再帰を扱うことができます。第5章「データと制御フロー」のlabelsの項で示したサンプルをnamed-lambdaで書いてみます。

(shadowing-import 'alexandria:named-lambda)
; => T

(defvar counter
  (named-lambda my-loop (lst sum)
    (if (null lst)
        sum
        (my-loop (cdr lst) (+ (reduce #'+ (car lst)) sum)))))
; => COUNTER

counter
; => #<FUNCTION MY-LOOP (LST SUM)
;      (BLOCK MY-LOOP
;       (IF (NULL LST) SUM (MY-LOOP (CDR LST) (+ (REDUCE #'+ (CAR LST)) SUM))))>

(funcall counter '((1 2 3) (4 5 6) (7 8 9 10)) 0)
; => 55

2018年1月30日火曜日

定義と束縛 - Alexandria

概要

このページではCommon Lispの汎用ユーティリティであるAlexandriaにおける定義・束縛系のオペレータを紹介します。

define-constantマクロ: 安全な定数の定義

Alexandriaのdefine-constantマクロは、定数を安全に定義できるオペレータです。

ANSI Common Lispにおける標準であるdefconstantはすでに使用されているシンボルを定数として定義してもエラーになりません。このような仕様だと名前の衝突が発生しても分からないまま使ってしまうというリスクがあります。

Alexandriaのdefine-constantdefconstantで定義を行う前に名前の衝突が発生していないかどうかを確かめてから定義してくれます。名前の衝突がある場合は Restart 付でエラーを通知します。

以下にサンプルを示します。

;; あらかじめインポートしておく
(shadowing-import 'alexandria:define-constant)
; => T

;; 変数を定義しておく
(defvar web "www.google.co.jp")
; => WEB

;; ANSI標準の defconstant は同じシンボルを定数として使用してもエラーにならない
(defconstant web "www.yahoo.co.jp")
; => WEB
web
; => "www.yahoo.co.jp"

;; Alexandria の define-constant は同じシンボルを使うとエラーになる
;; Restart は3種類用意されている
;;  - continue: 新しい値で定数を再定義する
;;  - ignore: 古い値を保持したままにする
;;  - abort: メインループに戻る(処理系が提供するrestart)
(define-constant web "www.google.co.jp")
; ** - Continuable Error
; WEB is an already defined constant whose value "www.yahoo.co.jp" is not equal
;      to the provided initial value "www.google.co.jp" under EQL.
; If you continue (by typing 'continue'): Try to redefine the constant.
; The following restarts are also available:
; IGNORE         :R1      Retain the current value.
; ABORT          :R2      Abort main loop

:r1
; => WEB

web
; => "www.yahoo.co.jp"

;; 元の値と新しい値が等価ならエラーは通知されない
;; :test キーワード引数で等価性判定述語を指定できる(標準は eql)
(define-constant web "www.yahoo.co.jp" :test #'string=)
; => WEB

if-letマクロ: letif

Alexandriaのif-letマクロは複数の条件判定を行い、全て真である場合にはthen-formを、1つでもnilがある場合はelse-formを実行します。ifandを組み合わせたような動作ですが、実際にはそれぞれの条件判定の結果を変数に束縛する機能があるため、ifandletを組み合わせたものとなります。
(if-let ((a 1) (b nil)) a b)
; => NIL

when-letマクロ: letwhen

when-letマクロは前節のif-letマクロの亜種です。if-letthen-formは1つの式に限られますので、もし複数式を実行したい場合は明示的にprognしなければなりません。when-letekse-formがない代わりにthen-formが暗黙的にprognされるため、複数の式を列挙することができます。それ以外はif-letと同じです。

when-let*マクロ: let*when

when-let*マクロは直線的な束縛を行うlet*形式のwhenです。
(when-let* ((a 1) (b a)) t)
; => T
letは並列的な束縛ですが、let*は直線的・逐次的な束縛です。そのため、上記サンプルのように、手前の束縛を使って次の束縛をすることができます。

2018年1月28日日曜日

データと制御フロー - Alexandria

概要

このページではCommon Lispの汎用ライブラリであるAlexandriaの制御系マクロを中心としたオペレータについて紹介します。

多くのオペレータはANSI Common Lisp標準仕様の第5章「データと制御フロー」を拡張するものですので、適宜参照してください。

switchマクロ: :test指定可能な分岐

ANSI Common Lispではcaseマクロが定められており、値による多方向分岐が可能ですが、caseマクロは値の判定に用いる述語関数を指定することができないため、基本的にeqlでは等価性を判定することのできない文字列などの多方向分岐には対応していません。数やシンボルによる分岐が中心です。

Alexandriaのswitchマクロはcaseマクロと似ていますが、述語関数を:testで指定することができるため、文字列の分岐に用いることができます。
;; あらかじめ switch をインポートしておく
(shadowing-import 'alexandria:swtich)
; => T

(defun switch-test (string)
  (switch (string :test #'string=)
    ("a" "A")
    ("b" "B")
    (otherwise "Unknown")))
; => SWITCH

(switch-test "a")
; => "A"

(switch-test "b")
; => "B"

(switch-test "c")
; => "Unknown"

switchマクロの第1引数が判定に用いる値で、リストの形式で指定します。その中で:testキーワード引数を使うと述語関数を指定することができます。

なお、totherwiseなどでデフォルトを指定するのではなく、エラーを発生させるeswitchマクロと、continueRestart)付でエラーを発生させるcswitchマクロもあります。

whicheverマクロ: どれか1つだけを評価する

使いどころはあまりよく分かりませんが、Alexandriaのwhicheverマクロは複数の値または式の中から1つだけを評価するというオペレータです。
(alexandria:whichever (princ 'a) (princ 'b) (princ 'c))
; B
; => B

xorマクロ: 排他的論理和

Alexandriaのxorマクロは「排他的論理和」を意味します。論理和orは「共に真」の時は「真」となりますが、排他的論理は「偽」となる点が異なります。
(shadowing-import 'alexandria:xor)
; => T

(xor 1 2)
; => NIL ;
;    NIL

(xor 1 nil)
; => 1 ;
;    T

(xor nil 2)
; => 2 ;
;    T

(xor nil nil)
; => NIL ;
;    T
ANSI Common Lispの標準にもありそうなオペレータですが、Alexandriaの中で定義されています。

nth-value-orマクロ: 多値のN番目

ANSI Common Lispの標準ではnth-valueマクロというものがあり、多値を返す式を評価してそのN番目の返り値を抜き出すことができます。Alexandriaのnth-value-orマクロはnth-valueマクロを複数式に対応させて拡張したもので、指定したN番目の返り値がnilの場合は次の式を評価し、nil以外になるまで複数式の評価を繰り返していきます。
(alexandria:nth-value-or 1 (values 1 nil) (values 2 nil) (values 3 t))
; => 3 ;
;    T

multiple-value-prog2マクロ: 2番目の式を多値で返す

ANSI Common Lispの標準ではmultiple-value-prog1マクロが定められており、複数式の最初の返り値を多値で返すことができますが、Alexandriaのmultiple-value-prog2マクロは名前の通り2番目の式を返します。
(alexandria:multiple-value-prog2 (values 1 nil) (values 2 nil) (values 3 t))
; => 2 ;
;    NIL

2018年1月27日土曜日

ハッシュテーブル - Alexandria

概要

このページではCommon Lispの汎用ユーティリティであるAlexandriaの「ハッシュテーブル」に関するオペレータを紹介します。

ハッシュテーブルはANSI Common Lispの標準仕様では第18章に定められているので、そちらも参照してください。

なお、このページでは以下の状態でハッシュテーブルを作成済みとしてサンプルを紹介します。
;; ASDFによりAlexandriaをロード
(asdf:load-system :alexandria)

;; ハッシュテーブルの生成
(defparameter *url* (make-hash-table))

;; データの登録
(setf (gethash 'google *url*) "https://www.google.co.jp/")
(setf (gethash 'yahoo *url*) "https://www.yahoo.co.jp/")
(setf (gethash 'my-site *url*) "http://lisp.satoshiweb.net/")

ensure-gethashマクロ: 拡張版gethash

ANSI Common Lispの標準ではgethashアクセッサが定められており、ハッシュテーブルの中からkeyを手掛かりにvalueを見つけることができます。このアクセッサの返り値は多値で、2つ目の返り値がpresent-p、すなわちkeyを持つデータがあったかどうかを示します。標準仕様入門の第18章「ハッシュテーブル」では独自のgethash*関数を定義し、対応するデータがない場合にコンディションシステムを発動させ、エラーを通知するようにしたサンプルを紹介しています。

Alexandriaのensure-gethashマクロはまた別の拡張であり、対応するデータがない場合には値を登録することができます。
;; nikkei というkeyを持つデータは存在しないが、
;; ensure-gethashなら第3引数を指定して登録できる
(alexandria:ensure-gethash 'nikkei *url* "https://www.nikkei.com/")
; => "https://www.nikkei.com/" ;
;    NIL

;; たしかに登録されている
(gethash 'nikkei *url*)
; => "https://www.nikkei.com/" ;
;    T

第3引数はオプショナルなので、省略した場合はnilが使われます。もちろん、対応するデータがある場合は上書きせず、そのデータ(keyに対応するvalue)を取得するだけです。

copy-hash-table関数: ハッシュテーブルのコピー

Alexandriaのcopy-hash-table関数はハッシュテーブルのコピーを行います。
(alexandria:copy-hash-table *url*)
; => #S(HASH-TABLE :TEST FASTHASH-EQL (GOOGLE . "https://www.google.co.jp/")
;       (YAHOO . "https://www.yahoo.co.jp/")
;       (MY-SITE . "http://lisp.satoshiweb.net/"))

maphash-keys関数: key専用のmaphash関数

ANSI Common Lisp標準仕様ではmaphash関数が定められているので、高階関数を用いたハッシュテーブルの繰り返し処理を行うことができます。しかし、maphash関数はkeyvalueを共に引数として取る関数のみを対象にしているので、どちらか片方だけを使いたい場合はdeclareシンボルでignoreする必要があります。

Alexandriaのmaphash-keys関数とmaphash-values関数はそのようなニーズに対応するためのもので、単純に使わない方をignoreした状態で定義されています。
(alexandria:maphash-keys #'(lambda (v) (print v)) *url*)
; 
; MY-SITE 
; YAHOO 
; GOOGLE 
; => NIL

(alexandria:maphash-values #'(lambda (v) (print v)) *url*)
; 
; "http://lisp.satoshiweb.net/" 
; "https://www.yahoo.co.jp/" 
; "https://www.google.co.jp/" 
; => NIL

maphash-values関数: value専用のmaphash関数

前節のmaphash-keys関数の説明を参照してください。

hash-table-keys関数: ハッシュテーブルのkeyを取得

節のタイトルの通り、ハッシュテーブルのkeyを取得してリストにして返します。また、hash-table-valuesはハッシュテーブルのvalueを取得してリストに返します。
(alexandria:hash-table-keys *url*)
; => (GOOGLE YAHOO MY-SITE)

(alexandria:hash-table-values *url*)
; => ("https://www.google.co.jp/" "https://www.yahoo.co.jp/"
;     "http://lisp.satoshiweb.net/")

hash-table-values関数: ハッシュテーブルのvalueを取得

前節のhash-table-keys関数の説明を参照してください。

ハッシュテーブルと連想・属性リストの相互変換

keyとvalueでデータを保存する構造はハッシュテーブルだけでなく、連想リストや属性リストなども同じです。連想リストとはコンスの形でkeyとvalueを保持する構造で、属性リストとはkeyとvalueが交互に現れるリストとして保持する構造です。

連想リストと属性リストについては第14章「リスト」を参照してください。

Alexandriaにはハッシュテーブルと連想・属性リストを相互に変換する関数が用意されています。

Function: hash-table-alist
ハッシュテーブルを連想リストに変換します。
Function: hash-table-plist
ハッシュテーブルを属性リストに変換します。
Function: alist-hash-table
連想リストをハッシュテーブルに変換します。
Function: plist-hash-table
属性リストをハッシュテーブルに変換します。
以下が変換のサンプルです。

(alexandria:hash-table-alist *url*)
; => ((GOOGLE . "https://www.google.co.jp/") (YAHOO . "https://www.yahoo.co.jp/")
;     (MY-SITE . "http://lisp.satoshiweb.net/"))

(alexandria:hash-table-plist *url*)
; => (GOOGLE "https://www.google.co.jp/" YAHOO "https://www.yahoo.co.jp/"
;     MY-SITE "http://lisp.satoshiweb.net/")

2018年1月22日月曜日

数 - Alexandria

概要

このページではCommon Lispの汎用ユーティリティであるAlexandriaの「数」に関するオペレータを紹介します。

clamp関数: 範囲内に収まるように調整

検査をしたい数が特定の範囲に収まる場合はその数を、収まらない場合は最も近い範囲内の数を返すという関数がclampです。
(alexandria:clamp -5 1 5)
; => 1
(alexandria:clamp 3 1 5)
; => 3
(alexandria:clamp 10 1 5)
; => 5
第1引数が検査をする数、第2・3引数が範囲です。

gaussian-random関数: 正規分布に従う乱数の生成

Common LispにはANSI標準で一様乱数を生成する関数が定められています(第12章「」)。また、MT19937という良質な擬似乱数生成ライブラリもあるため、一様乱数が必要な時はこれらを使用することができます。

Alexandriaのgaussian-random関数はガウス確率、すなわち正規分布に従う乱数を生成する関数です。0.0d0を基準とする乱数を生成します。引数はオプショナルで、範囲を指定することも可能です。
(alexandria:gaussian-random -1 1)
; => 0.8726597297126646d0 ;
;    -0.17264114012888226d0

iota関数: 数列の生成

iota関数は定番の数列生成オペレータです。使い方は3通りあります。

まず、引数を1つだけ指定すると0から指定した数個の数列を作ります。
;; 定番なので、あらかじめインポートしておく
(shadowing-import 'alexandria:iota)
; => T

(iota 5)
; => (0 1 2 3 4)

次に、:startキーワードで開始位置を指定することもできます。
(iota 5 :start 10)
; => (10 11 12 13 14)

最後に、:stepキーワードで間隔を指定することができます。
(iota 5 :step 5)
; => (0 5 10 15 20)

Common Lispは分数もネイティブにサポートしているので、当然このような分数の数列を生成することも可能です。
(iota 5 :step 1/3)
; => (0 1/3 2/3 1 4/3)

map-iota関数: 数列のマッピング(高階関数)

map-iota関数は生成した数列に対してマッピングを行います。ただし、マッピングの結果は返り値とはなりませんので、副作用を目的として使用されます。
(defun tri (n) (print (expt n 3)))
; => TRI

(alexandria:map-iota #'tri 10)
; 
; 0 
; 1 
; 8 
; 27 
; 64 
; 125 
; 216 
; 343 
; 512 
; 729 
; => 10

lerp関数: 線形補間

Alexandriaのlerp関数は最も基本的な補間アルゴリズムである線形補間(Linear interpolation)を行うオペレータです。第1引数に補間係数を、第2・3引数に点の座標を与えます。

ここでは、CLOS (Common Lisp Object System) を用いた二次元座標における線形補間のサンプルを示します。CLOSの詳細については第4章「型とクラス」及び第7章「オブジェクト」を参照してください。
;; 2次元座標における点を示すpointクラスを定義する
(defclass point ()
  ((x :initarg :x
      :accessor point-x)
   (y :initarg :y
      :accessor point-y)))
; => #<STANDARD-CLASS POINT>

;; print-object総称関数を定義しておく
(defmethod print-object :before ((p point) stream)
  (format stream "(x, y) = (~a, ~a)~%" (point-x p) (point-y p)))
; => #<STANDARD-METHOD :BEFORE (#<STANDARD-CLASS POINT> #<BUILT-IN-CLASS T>)>

;; 補間係数と2点を取り、補間後の点オブジェクトを返す point-lerp 関数
(defmethod point-lerp ((v real) (a point) (b point))
  (make-instance 'point
                 :x (alexandria:lerp v (point-x a) (point-x b))
                 :y (alexandria:lerp v (point-y a) (point-y b))))
; => #<STANDARD-METHOD
;   (#<BUILT-IN-CLASS REAL> #<STANDARD-CLASS POINT> #<STANDARD-CLASS POINT>)>

;; サンプル点1
(defparameter p1 (make-instance 'point :x 1 :y 3))
; => P1
;; サンプル点2
(defparameter p2 (make-instance 'point :x 5 :y 8))
; => P2

;; point-lerp 関数を試す
(point-lerp 1/4 p1 p2)
; (x, y) = (2.0, 4.25)
; => #<point x000000020043aa91="">

mean関数: 平均

Alexandriaのmean関数はリストやベクトルなどのシーケンスの平均を求める関数です。
;; リスト
(alexandria:mean '(1 2 3 4 5))
; => 3

;; ベクトル
(alexandria:mean #(1 2 3 4 5))
; => 3

median関数: 中央値

Alexandriaのmedian関数はシーケンスの中央値を求める関数です。
(alexandria:mean '(2 5 1 3 4))
; => 3

variance関数: 分散

variance関数は分散を求める関数です。分散とは、平均との差分の2乗の和を個数で割ったものです。確率論では平均が「1次のモーメント」、分散が(中心まわりの)「2次のモーメント」と呼ばれます。

例えば'(1 2 3 4 5)の平均は3なので、それぞれの値の3との差分を取ると'(-2 -1 0 1 2)となります。そしてそれを2乗すると'(4 1 0 1 4)となり、その和は10となります。最後に10を要素数である5で割ると2になります。
(alexandria:variance '(1 2 3 4 5))
; => 2
なお、統計の世界では要素数で直接割るのではなく、1を引いてから割ることが多いです。これは「自由度」と呼ばれています。分散は平均との差分を使って求めますが、基準(平均)となる値1つ分は基準から離れることができないため、5ではなく4を使って割ります。このような分散を「標本分散」と呼び、「母分散」と区別されます。

Alexandriaでは:biasedキーワード引数をnilにすることで標本分散を得ることができます。
(alexandria:variance '(1 2 3 4 5) :biased nil)
; => 5/2

standard-deviation関数: 標準偏差

分散は値の散らばりを見るための指標ですが、分散を求める際に2乗しているので、元の単位が失われています。そこで元の値の単位と同じにするために分散を平方根した「標準偏差」がよく使われます。Alexandriaではstandard-deviation関数で求めることができます。
(alexandria:standard-deviation '(1 2 3 4 5) :biased nil)
; => 1.5811388
なお、テストでよく聞く「偏差値」は平均を50、標準偏差を10として標準化した指標です。分布が正規分布に近い場合は、例えば偏差値70は平均点を2標準偏差上回っていることを意味するので、全体の上位約2〜3%であることを意味します。

maxf, minf関数: 最大値・最小値のモディファイマクロ

ANSI Common Lispではmax関数とmin関数が定められており、最大値と最小値は標準仕様の範囲内で求めることができます。Alexandriaが提供するmaxfminfは「モディファイマクロ」で、求めた最大値・最小値を変数に束縛するところまでを提供します。
;; 変数の準備
(defparameter v 0)
; => V
v
; => 0

;; モディファイマクロを使用
(alexandria:maxf v 1 2 3 4 5)
; => 5

;; 変数の値が変わっている
v
; => 5

モディファイマクロはdefine-modify-macroマクロを使って簡単に定義できます。Alexandriaのmaxfminfはそれぞれ以下のように定義されています(ドキュメント文字列は省略)。
;; maxfの定義
(define-modify-macro maxf (&rest numbers) max)

;; minfの定義
(define-modify-macro minf (&rest numbers) min)

配列 - Alexandria

概要

このページではCommon Lispの汎用ユーティリティであるAlexandriaの配列に関する型と関数を紹介します。

配列に関する型

Common Lispではdeftypeマクロでオリジナルの「型」を定義することができます。型はクラスや構造体を定義するときに付随して定義されますが、型単独で定義することも可能です。(詳しくはANSI Common Lisp仕様入門の第4章「型とクラス」を参照してください。)

Alexandriaでは配列に関する型が2種類定められています。
Type: array-index
配列の要素番号を示す型で、0以上指定の数未満を意味します。
Type: array-length
配列の要素番号を示す型で、0以上指定の数以下を意味します。
2つの方の違いは「未満」か「以下」です。例えば、3つの要素を持つ配列の要素番号は0, 1, 2のいずれかであり、3未満となりますが、要素数は3です。

以下で例を示します。
;; あらかじめ型をインポートしておく
(shadowing-import 'alexandria:array-index)
; => T
(shadowing-import 'alexandria:array-length)
; => T

;; 未満と以下で動作が異なる点に注意
(typep 3 '(array-index 3))
; => NIL

(typep 3 '(array-length 3))
; => T

型を指定するとcheck-typeマクロを使うことができるため、例外処理を簡単に記述することができるのが便利です。詳しくは仕様入門の第4章「型とクラス」のcheck-typeマクロの節を参照してください。

copy-array関数: 配列のコピー

ANSI Common Lispでは配列を完全にコピーできる関数は用意されていません。Common Lispの配列にはフィルポインタなどの様々な機能が組み込まれており、自分で配列をコピーするのは意外と大変です。

そこでAlexandriaにはcopy-arrayという標準仕様にもありそうな関数が定義されています。
(defparameter *array* (make-array 10 :element-type 'fixnum 
                                     :initial-element 0
                                     :adjustable t
                                     :fill-pointer 3))
; => *ARRAY*

(equalp *array* (alexandria:copy-array *array*))
; => T
ただし、:adjustable:fill-pointerを付けた配列へのアクセスはsimple-vectorへのアクセスに比べてかなり遅くなります。複雑な配列のコピーは自分の行う計算をよく考えて使用してください。

2018年1月21日日曜日

シンボル - Alexandria

概要

このページでは、Common Lispの汎用ライブラリであるAlexandriaのシンボルに関するオペレータを紹介します。

ensure-symbol関数: シンボルのインターン

ANSI Common Lispでは第11章「パッケージ」でinternという関数が定められており、文字列とパッケージを引数に取ることでその文字列からシンボルを生成し、パッケージに所属させることができます。

intern関数の第1引数は文字列でなければなりませんが、Alexandriaのensure-symbol関数はシンボルを引数に取ることができるため、大文字にする必要はありません。
(alexandria:ensure-symbol 'hello)
; => HELLO ;
;    :INTERNAL

(export 'hello)
; => T

(alexandria:ensure-symbol 'hello)
; => HELLO ;
;    :EXTERNAL
動作はintern関数と同じです。返り値の1番目は生成されたシンボル、2番目はシンボルの状態です。もちろん、該当するシンボルが存在する場合は新たに生成されることはありません。

format-symbol関数: シンボルの完全なフォーマット

シンボルを完全な形式で表示したい場合はformat-symbol関数を使うことができます。
(shadowing-import 'alexandria:format-symbol)
; => T

;; 普通のformatはシンプルな出力を行う
(format nil "~s" 'hello)
; => "HELLO"

;; Alexandriaのformat-symbolは丁寧な出力を行う
(format-symbol t "~s" 'hello)
; => |\|COMMON-LISP-USER\|::\|HELLO\||

;; 第1引数をnilにするとインターンされていないシンボルになる
(format-symbol nil "~s" 'hello)
; => #:|\|COMMON-LISP-USER\|::\|HELLO\||

;; パッケージを指定することもできる
(format-symbol :alexandria "~s" 'with-gensyms)
; => ALEXANDRIA.0.DEV::|\|EXT\|::\|WITH-GENSYMS\||

make-keyword関数: キーワードシンボルの生成

Common Lispにおいて「キーワード」とは、KEYWORDパッケージに属するシンボルのことです(参考:第11章「パッケージ」)。キーワードはコロン:を付けて示すことで即KEYWORDパッケージにインターンされます。そのため、明示的な生成は不要です。

Alexandriaのmake-keyword関数はキーワードの生成を関数を通じて明示的に行いたい場合に利用します。
(alexandria:make-keyword "FOO")
; => :FOO ;
;    :EXTERNAL

make-gensym関数: 数または文字列を使用したユニークシンボルの生成

ANSI Common Lispのgensym関数は通常何も引数を付けずに評価してランダムなユニークシンボル(どのパッケージにも属さないシンボル)を生成しますが、数や文字列を指定しすることもできます。

Alexandriaのmake-gensym関数はその機能を少し拡張子、文字列の代わりに文字列指定子となるようなシンボルまたは文字でもgensym関数が動作するようにしているものです。
(alexandria:make-gensym #\a)
; => #:|a9207|

(alexandria:make-gensym 'hello)
; => #:HELLO9206
これらはいずれもgensym関数ではエラーになります。

なお、gensym関数に数の引数を与えることはANSI標準で非推奨とされているので注意してください。

make-gensym-list関数: 複数のユニークシンボルの生成

変わり種のオペレータもあります。複数回のgensymを行い、ユニークシンボルをいくつも作るというオペレータです。
(alexandria:make-gensym-list 5)
; => (#:G9218 #:G9219 #:G9220 #:G9221 #:G9222)
第2引数はオプショナルで、make-gensym関数と同じような指定をすることも可能です。

symbolicate関数: シンボルと文字列の連結してシンボルの生成する

複数のシンボルや文字列を連結して、新しいシンボルを生成する関数も用意されています。symbolicate関数です。
(alexandria:symbolicate 'hello "-World")
; => |HELLO-World|

2018年1月20日土曜日

マクロの記述 - Alexandria

概要

このページではCommon Lispの汎用ライブラリであるAlexandriaのマクロに関するオペレータを紹介します。

with-gensymsマクロ: ユニークなシンボルの生成と束縛

ANSI Common Lispではgensymという関数が定められており、どこでも使われていないユニークなシンボルを生成することができます。マクロ定義の中で変数を使うときはこのgensym関数を使うことで変数の予期せぬ誤参照を防ぐことができますが、多くの変数を扱う時には一つ一つ(gensym)してその返り値を束縛するため、記述が冗長になりがちです。

そこでAlexandriaには複数のユニークなシンボルを生成して束縛するマクロが用意されています。それがwith-gensymsマクロで、おそらくAlexandriaのユーティリティの中でも定番中の定番と言えるオペレータになっています。
(shadowing-import 'alexandria:with-gensyms)
; => T
Alexandriaのユーティリティを使うときにはalexandria:with-gensymsというようにパッケージ名をつける必要がありますが、マクロの記述はコード量を減らすために使われるため、よく使うと考えられるオペレータはインポートしてしまうのが良いでしょう。shadowing-importについては仕様入門の第11章「パッケージ」を参照してください。

実際にユニークな変数を使用するときは、単に第1引数のリストにそのシンボルを列挙します。
(with-gensyms (x y)
  (format t "(x, y) = (~s, ~s)~%" x y))
; (x, y) = (#:X9115, #:Y9116)
; => NIL
シャープコロン(#:)は「インターンされていないシンボル」を示すリーダーマクロです。ここではxyという変数を使っているように見えますが、実際にはユニークなシンボルに置き換わっているのが分かります。

実際にマクロの中で使用する場合は以下のようになります。

例えば、代表的なswapマクロの定義は以下の通りです。
(defmacro swap (x y)
  (let ((temp (gensym)))
    `(let ((,temp ,x))
       (setf ,x ,y)
       (setf ,y ,temp))))
ここでwith-gensymsを使用すると、以下のように記述することができます。
(defmacro swap (x y)
  (with-gensyms (temp)
    `(let ((,temp ,x))
       (setf ,x ,y)
       (setf ,y ,temp))))
使用する一時変数の数が多い場合には特に便利ですので、積極的に使ってください。

once-onlyマクロ: 一度だけの評価を保証するマクロ定義

マクロの定義で注意が必要なのは、不用意な変数束縛だけではありません。評価の回数も注意が必要です。

例えば、引数の値をCARCDRにセットしたコンスを返す関数cons1を定義します。
(defun cons1 (x)
  (cons x x))
; => CONS1
この関数は、どのような引数でも正常に動作します。
(cons1 5)
; => (5 . 5)

(cons1 (random 10))
; => (7 . 7)

ところが、同じ動作をマクロで実装しようとすると、乱数などの際に誤作動が生じます。
(defmacro cons1 (x)
  `(cons ,x ,x))
; => CONS1

(cons1 5)
; => (5 . 5)

(cons1 (random 10))
; => (5 . 8)
これは、5という定数の場合は何度評価しても5ですが、(random 10)という式は評価するために10未満の擬似乱数を発生させるため、値が変わってしまうのです。

この問題はマクロ定義の中でAlexandriaのonce-onlyマクロを介することで解決できます。
(shadowing-import 'alexandria:once-only)
; => T

(defmacro cons1 (x)
  (once-only (x)
    `(cons ,x ,x)))
; => CONS1

(cons1 (random 10))
; => (6 . 6)
複数回の評価を避けたい場合には便利ですので活用してください。

parse-body関数: 定義本文の解析

あまり使うことはないと思いますが、関数の定義をコードではなくデータとして解釈するオペレータも用意されています。

例えば、以下のexpt2関数定義をデータとしてパースしてみます。
(defparameter func
  '(defun expt2 (x)
    (declare (type real x))
    "Equal to (expt x 2)."
    (expt x 2)))
; => FUNC

(cdddr func)
; => ((DECLARE (TYPE REAL X)) "Equal to (expt x 2)." (EXPT X 2))

(alexandria:parse-body (cdddr func)
                       :documentation t)
; => ((EXPT X 2)) ;
;    ((DECLARE (TYPE REAL X))) ;
;    "Equal to (expt x 2)."
関数の定義本体部分をAlexandriaのparse-body関数に渡すと、定義・宣言・ドキュメント文字列の3つの多値が返されます。

destructuring-caseマクロ: ラムダリスト形式のマッチング

Alexandriaには簡易的なマッチングオペレータも付属しています。ANSI Common Lispで仕様として定められているcaseマクロ(第5章「データと制御フロー」)とdestructuring-bindマクロ(第3章「評価とコンパイル」)を組み合わせたdestructuring-caseマクロです。

このマクロはなんでもマッチングできるわけではなく、基本的にはラムダリスト形式の式、つまり関数の引数定義に用いるような形式でのマッチングを行います。
(defun match (v)
  (destructuring-case v
    ((:foo x y) (format nil "Arguments: (x = ~s, y = ~s)" x y))
    ((:bar &key x y) (format nil "Keywords: (x = ~s, y = ~s)" x y))
    ((t &rest rest) (format nil "Unkown: ~s" rest))))
; => MATCH

(match '(:foo 1 2))
; => "Arguments: (x = 1, y = 2)"

(match '(:bar :y 1 :x 2))
; => "Keywords: (x = 2, y = 1)"

(match '(:something 1 2))
; => "Unkown: (1 2)"
キーワード引数のようなマッチングも行うことができるので、リスト内の順序に影響されにくいマッチングを行うことが可能です。

IO - Alexandria

概要

このページではCommon Lispの汎用ライブラリであるAlexandriaのIOパートについて説明します。

with-input-from-fileマクロ: ファイルの読み込み

with-input-from-file関数は常に:direction:inputで扱うwith-open-fileマクロとほぼ同等です。つまり、入力であることを明示的に扱います。
(alexandria:with-input-from-file (in "test.txt")
  (read-line in nil))
; => "This is a test.";
;    NIL

この例では、"test.txt"というファイルがある前提で、入力を行なっています。

なお、「ほぼ同等」と書いたのは、直接with-open-fileマクロを使用しているのではなく、with-open-file*という内部マクロでラップして使用しているためです。この内部マクロはキーワード引数に:nilをしていした場合、そのキーワード引数は指定しなかったのと同じ扱いとなり、デフォルト値が使用されるという点がANSI標準のwith-open-fileと異なります。

with-output-to-fileマクロ: ファイルへの書き込み

with-output-to-file関数は常に:direction:outputで扱うwith-open-fileマクロとほぼ同等です。つまり、:directionキーワード引数を使わなくてもファイルへの書き込みに使うことができます。

使い方はwith-input-from-fileマクロと同じなので、サンプルは省略します。

read-stream-content-into-string関数: 文字列への一括入力

read-stream-content-into-string関数はストリームを引数に取り、文字列を一括して読み取って返す関数です。
(alexandria:with-input-from-file (in "test.txt")
  (alexandria:read-stream-content-into-string in))
; => "This is a test.
;    これはテスト。
;    "
第2引数は:buffer-sizeというキーワード引数で、デフォルトでは4096に設定されています。特に指定する必要はありません。

read-file-into-string関数: ファイルからの一括入力(文字列)

テキストファイルの内容を文字列として一括入力したい場合はread-file-into-string関数を使うことができます。
(alexandria:read-file-into-string "test.txt")
; => "This is a test.
;    これはテスト。
;    "

テキストファイルの文字符号化方式を指定したい場合は:external-format引数を使用します。ただし、Babelのような文字列処理に特化したライブラリではないので、文字符号化方式の指定方法は各処理系の記述方法に依存します。

write-string-into-file関数: ファイルへの一括出力(文字列)

文字列をテキストファイルに一括して出力するにはwrite-string-into-file関数を使用します。
(alexandria:write-string-into-file "Hello,World" "test2.txt"
                                   :if-exists :supersede)
; => "Hello,World"

ファイルに対するキーワード引数についてはANSI Common Lisp仕様入門の第21章「ストリーム」を参照してください。:supersedeは上書きですが、標準では:errorになっているため、既存の同名ファイルが存在するとエラーになります。

read-file-into-byte-vector関数: ファイルからの一括入力(バイトベクトル)

read-file-into-string関数とよく似ていますが、返り値は文字列ではなくバイトベクトルです。
(alexandria:read-file-into-byte-vector "test.txt")
; => #(84 104 105 115 32 105 115 32 97 32 116 101 115 116 46 10 227 129 147 227 130
;    140 227 129 175 227 131 134 227 130 185 227 131 136 227 128 130 10)

なお、read-stream-content-into-string関数と同様にread-stream-content-into-byte-vector関数も使うことができます。ストリームを別途開いている場合はこちらの関数を使用します。

write-byte-vector-into-file関数: ファイルへの一括出力(バイトベクトル)

こちらはwrite-string-into-file関数のバイトベクトル版です。サンプルは省略します。

copy-file関数: ファイルのコピー

ANSI Common Lispの標準仕様ではファイルのコピーに関する関数が定められていないため、いくつかのライブラリが代わりに提供しています。Alexandriaのcopy-file関数は以下の機能でファイルをコピーします。
  • :element-typeキーワードは'(unsigned-byte 8)を仕様(テキストでもバイナリでもコピーできる)
  • コピー先に同名のファイルが存在する場合は:supersede(上書き)
  • バッファサイズは4096
なお、返り値はコピーしたバイト(オクテット)の数です。
(alexandria:copy-file "test.txt" "test2.txt")
; => 38

2018年1月18日木曜日

Lisp Vision

概要

このページではCommon Lisp及び「独学 Common Lisp」のビジョンについて記述します。

独学 Common Lisp の目的

このサイトの目的は「Common Lispユーザーの裾野を広げること」です。

解説

健全なプログラミング言語には健全なユーザーが存在します。トップハッカーから初心者まで、幅広い層のユーザーがおり、知識を共有しあって言語を維持・発展させていきます。

しかし、MITのようなアカデミックな領域で開発されてきたCommon Lispには、他の言語に見られるようなユーザーの幅広い裾野が存在しません。Lispの文化を知ろうともしない多くの普通のプログラマと、Lispの魅力に取り憑かれてしまったごく少数のハッカーが存在します。プログラムやライブラリはごく少数のハッカーだけでも生産できますが、健全なユーザーコミュニティや知識の共有は一定のユーザーが存在しなければ育まれないのです。

私は特に、趣味プログラマを重視しています。自分自身が中学生からプログラミングを始め、様々な言語を学んできましたが、私と同じように「愛用の言語」としてLispを選ぶのは、様々な言語の習得を通じて言語の多様性を学び、結果的にLispにたどり着いた、という人になるのではないかと考えているからです。仕事でプログラミングをするならC, Java, Pythonを身につけるでしょうが、それでは満足できない一部のプログラマがLispを求めるのではないかと考えます。しかし、そのようなプログラマも、必ずしもLisp自体のスキルについてトップレベルにまで引き上げていくということは少なく、単にLispを知りたい、学んでみたいという動機で学ぶことも多いのではないかと思うのです。

このサイトでは、知的好奇心を満たすためにLispを学ぶ方を主な読者として想定しますが、数学的・研究的にはならないように心がけます。なぜなら、プログラマの問題関心は非常に多様であり、文字列処理が好きな人もいれば画像処理が得意な人もおり、データベースを使いたい衝動に駆られている人もいるだろうと考えるからです。そのため、特定の領域の知識・技術を深く紹介することよりも、様々な領域の知識を浅く広く紹介することを重視しています。

以上の背景を踏まえ、当サイトでは「Common Lispユーザーの裾野を広げること」を目的としてコンテンツを構築していきます。

Common Lispの過去・現在・未来

このサイトではCommon Lispユーザーの裾野を広げることを目的とする一方で、Common Lispが産業界で広く使われるようになることを目指すものではありません。Common Lispがミドルスキルプログラマ以上にとっての「教養(リベラルアーツ)」となることを目指します。

解説

Lispは過去において、普及という面では成功と失敗を共に経験してきました。しかし、私はLispの過去の栄光を紹介して復権を試みるのではなく、プログラミング言語としてのLispそのものに価値を見出しています。

ANSI Common Lispは純粋に完成された言語仕様であり、時代が移り変わっても更新が必要であるとは考えていません。私はLispに対して工学ではなく数学・哲学であることを望みます。(Lisp自体は数学的な美しさがあるものと考えますが、このサイトを数学的にしたいとは考えていません。)

プログラミングは2020年度から小学生でも学ぶようになり、特にPythonのようなシンプルな言語はこれからも普及していくでしょう。しかし、優秀なビジネスマンが母国語・英語・会計・経営・法務・税務・ITなど多様なスキルを身につけるように、これからは多くの優秀なプログラマが多様な問題領域に多様な方法でアプローチする必要が出てくると予想します。全ての自然言語が英語に集約せず多様性が維持されているように、プログラミング言語の多様性もまた増えていくと考えます。

このような将来的なプログラミング動向を見据えた時に、知的思考・知的生産の一助になる言語がLispであると考えます。

Lispの最大の特徴で、他の言語が模倣できない点が「コードをデータで表すこと」です。言語には固有の歴史的出自があり、どの言語も長所と短所を併せ持っていますが、「コードを処理するコード」をLispほど簡単に扱える言語は存在しません。Lispはそれ自体が「メタ」、すなわち形而上学的な特質を持っています。

この特徴はLispの理解を難しくしている一方で、Lispに多大なる自由を与えています。多様な問題領域に直面する将来のプログラマに求められるのは自然言語の設計図をプログラミング言語に置換する処理ではなく、プログラミング言語の論理で設計を考える能力であり、俯瞰する力(メタ認知)であると考えます。

Common LispはANSIによって標準化された唯一のLispであり、一度学んだことはそう簡単に失われません。小学生が常用漢字を学ぶように、高校生が様々な数学の公式を覚えるように、大学生が哲学を学ぶように、ミドルスキル以上を目指すプログラマは(たとえよく使う言語がJavaであっても)一度はCommon Lispに触れるべきであると考えます。

これが、私の考えるCommon Lispの未来です。

私たちにできること

私よりプログラミング能力が高い人はたくさんいるので、私はCommon Lispに関する日本語の情報を蓄積することでCommon Lispユーザーの裾野を広げ、将来の「独学者」の糧になるように備えたいと考えています。

教育機関・教員にできること

LispはMITを中心とする大学で成長してきたので、我が国でもLispを扱う教育機関・教員は相応に存在すると考えています。

しかし、Lispの教育界隈はあまりにも「アルゴリズムとデータ構造」を重視しすぎており、オブジェクト指向や例外処理などプログラミングの「現場」を軽視しすぎてきました。結果として末尾再帰と継続を習得することだけに専念してしまい、学習者はLispを非力で非現実的な言語として認識することが多かったのではないかと思います。

このサイトでは「Common Lisp 仕様入門」を通じてANSI標準のLispを学ぶことができるようにしています。Lispには多次元がネイティブにサポートされる配列や動的な性格をもつクラス、多重ディスパッチをサポートする総称関数など、末尾再帰と継続以外にも学ぶべき項目はたくさんあります。Common Lispの言語仕様を俯瞰して見た上で、学習者が何を身につけるべきかを再検討してみてはいかがでしょうか。

産業界にできること

Quicklispの登場以後、Common Lispは格段に実用的に(使いやすく)なりました。サードパーティ製ライブラリの充実は言語の健全な発展に必要不可欠であり、このサイトでも「Common Lisp ライブラリ入門」を通じて様々なライブラリを紹介していきたいですし、産業界のリアルプログラマ・ハッカーにはさらなる充実を期待しています。

しかし、ライブラリはいくら充実させていっても、PerlやPythonに勝つことはないでしょう。既存のライブラリが豊富に存在することはPythonにとっては生命線ですが、Common Lispにとっては必ずしも生命線ではありません。

産業界がCommon Lispユーザーの裾野を広げるための最大の貢献は、ユースケースを紹介することだと考えます。

私はiRobot社のルンバや量子コンピュータメーカーD-WaveのOSでCommon Lispが使われていると知り、Common Lispを使っていこうという決意をしました。Common Lispの知識は多くのプログラマの教養として広く浸透すべきですが、実際に使われる場面は「要所」であることが望ましいのではないかと思います。つまり、Webアプリケーションのような領域はPerlでもPythonでもRubyでも実装できますが、いくつかの領域は「あえてCommon Lispを選んだ」というポイントがあるのではないかと思うのです。

(参考)
Yahoo! ショッピング(旧Viaweb)を立ち上げたPaul GrahamがViawebを売却するまでCommon Lispを使っているということを公表しなかったように、技術情報を公表することは難しいかもしれません。しかし、技術的背景が特異であるということは、差別化の観点からも有効であるはずです。もちろん、ここでいう差別化とは顧客に対するサービスの差別化ではなく、従業員であるプログラマを募集する際の差別化です。

Common Lispを実際に使っている企業はプログラマの募集の際に実際のユースケースを紹介するなどして、Common Lispが固有の特徴を持つ言語であることを産業界から認めることが重要なのではないかと考えます。

まとめ

  1. 「独学Common Lisp」はCommon Lispユーザーの裾野を広げることを目的として日本語のドキュメントを蓄積していきます。
  2. Common Lispは工学・ツールではなく哲学・教養、すなわちプログラマのリベラルアーツとして広く認知されることを将来的に期待しています。
なお、このサイトでは日本語の情報を蓄積していきますが、参照先は英語の一次情報を優先して使います(ANSI標準やライブラリのマニュアル、ドキュメント文字列などを参考に記述していきます)。

また、ブログのシステムを利用して更新していきますが、情報はストックされなければ意味がないと考えているので、時とともに後ろに流れるのではなく、適切なガイドページを用意して時間的に普遍な価値を持つ情報を「蓄積」したいと考えています。

=====平成30年1月18日 第1版=====

2018年1月16日火曜日

Lisp Links

概要

このページでは、Common Lispに関するドキュメント(主に日本語)を紹介します。

入門講座

管理人が自信を持ってオススメできる体系的な解説ドキュメント。

formatloop

よく使うオペレータ二大巨頭に関する解説記事。使うときに開いておくページ。

オブジェクト・コンディション・パッケージ

大学などの教育機関ではあまり教えられないが、Common Lispの重要な根幹である3つの「システム」に関するドキュメント。

日本語と処理系

仕様頻度は少ないものの、覚えてはいないので使うときには必ず見る、というページ。

その他

ご一読ください。

英語でも読みたい厳選サイト

2018年1月14日日曜日

Alexandria: 汎用定番ユーティリティ

概要

AlexandriaはCommon Lispにおける最も定番のユーティリティライブラリです。

特定の機能を持つ関数を集約したものではなく、様々なライブラリで使われるようなより汎用性の高いオペレータを扱っています。

ライセンスもパブリックドメインであり、自分のツールに同梱することもできますし、AlexandriaはほとんどのCommon Lispユーザーに使われているので、ASDFかQuicklispで簡単にロードできる状態になっているのが一般的です。

Alexandriaの関数は非常に多岐に渡るため、このページでは少しずつ更新しています。また、Alexandriaにはドラフト版のマニュアルも存在しますが、マニュアルには書いていないオペレータも定義されていることがあるため、このページでは実際のソースコードを見ながら定義内容とドキュメント文字列を基にサンプルベースで使い方を紹介します。

目次

  1. データと制御フロー
  2. 定義と束縛
  3. マクロの記述
  4. 関数
  5. シンボル
  6. リスト
  7. 配列
  8. シーケンス
  9. ハッシュテーブル
  10. IO(入力・出力)

2018年1月10日水曜日

第25章「周辺分野」

概要

ANSI Common Lispの第25章「周辺分野」を説明します。

時間に関するオペレータ

ANSI Common Lispの第25章は Environment で、直訳すると「環境」ですが、要はLispの言語そのものとは関係がない周辺の分野についてまとめてある章です。そこで、このページでは主に時間に関するオペレータと処理系に関するオペレータを紹介します。

まず、これまでにも何回か使ってきた、現在時刻を取得するオペレータです。時刻に関する情報は言語の中にはなく、ハードウェアとOSによって提供されるため、Lispの外の問題です。get-universal-time関数とget-decoded-time関数が現在の時刻を取得してくれます。
(get-universal-time)
; => 3722764826

(get-decoded-time)
; => 34 ;
;    20 ;
;    22 ;
;    20 ;
;    12 ;
;    2017 ;
;    2 ;
;    NIL ;
;    -9

なお、ユニバーサルタイムと日時の情報を相互に変換するにはdecode-universal-time関数とencode-universal-time関数を使います。

もう一つ、時間に関してよく使うオペレータがtimeマクロです。これは、引数を評価する時間を計測するマクロです。
(time (loop for i from 1 to 1000000 summing i))
; Real time: 8.329995 sec.
; Run time: 8.326985 sec.
; Space: 9656 Bytes
; => 500000500000

どのような情報が表示されるかは処理系依存ですが、とても便利なマクロです。

処理系に関するオペレータ

処理系に関するオペレータは大きく分けて3種類紹介します。

まず、traceマクロです。このマクロは特定の関数の実行状況をトレースして表示してくれます。末尾再帰を多用するSchemeではよく学習に使われますが、Common Lispにはloopマクロがあるので、あまり学習目的でも使われていないかもしれません。

例えば、再帰を用いてリストの要素数を求める関数を書いてみます。
(defun my-length (list n) 
  (if (endp list) 
      n 
      (my-length (cdr list) (1+ n))))
; => MY-LENGTH
この関数をトレースしてみます。
(trace my-length)
;; Tracing function MY-LENGTH.
; => (MY-LENGTH)

(my-length '(a b c d e) 0)
; 1. Trace: (MY-LENGTH '(A B C D E) '0)
; 2. Trace: (MY-LENGTH '(B C D E) '1)
; 3. Trace: (MY-LENGTH '(C D E) '2)
; 4. Trace: (MY-LENGTH '(D E) '3)
; 5. Trace: (MY-LENGTH '(E) '4)
; 6. Trace: (MY-LENGTH 'NIL '5)
; 6. Trace: MY-LENGTH ==> 5
; 5. Trace: MY-LENGTH ==> 5
; 4. Trace: MY-LENGTH ==> 5
; 3. Trace: MY-LENGTH ==> 5
; 2. Trace: MY-LENGTH ==> 5
; 1. Trace: MY-LENGTH ==> 5
; => 5

この関数をコンパイルして、もう一度評価してみます。
(compile 'my-length)
; => MY-LENGTH ;
;    NIL ;
;    NIL

(my-length '(a b c d e) 0)
; 1. Trace: (MY-LENGTH '(A B C D E) '0)
; 1. Trace: MY-LENGTH ==> 5
; => 5

関数呼び出しの処理が大幅に短縮化されているのが分かります。traceマクロは特定の関数が呼び出される時に、その呼び出しと評価のプロセスを表示するものです。

traceは関数の実行状況をトレースするオペレータですが、ANSI Common Lispではドキュメントを表示するオペレータも定められています。それがdocumentation総称関数で、関数やマクロなどの定義の中に含まれる「ドキュメント文字列」を取り出すことができます。

例えば、Alexandriaのwith-gensymsマクロは非常によく利用されますが、そのドキュメントをみたければ、ASDFなどでAlexandriaをロードしてから、以下のようにdocumentation総称関数を呼び出します。
(documentation 'alexandria:with-gensyms 'function)
; => "Binds each variable named by a symbol in NAMES to a unique symbol around
; FORMS. Each of NAMES must either be either a symbol, or of the form:
; 
;  (symbol string-designator)
; 
; Bare symbols appearing in NAMES are equivalent to:
; 
;  (symbol symbol)
; 
; The string-designator is used as the argument to GENSYM when constructing the
; unique symbol the named variable will be bound to."
with-gensymsはマクロですが、documentationの第2引数は'functionで構いません。
最後に紹介するのは、インタプリタのみで利用できるコマンドです。

このサイトでも何度か使ってきましたが、*スペシャル変数は一つ前の式の評価結果を使うことができます。
(+ 1 2)
; => 3

*
; => 3

返り値ではなく、評価した式自体を取り出したい場合は+スペシャル変数を使います。
(+ 1 2)
; => 3

+
; => (+ 1 2)

手前の評価式ではなく、今の評価式自体を取り出したい場合は-スペシャル変数を使います。
(format t "~a" -)
; (FORMAT T ~a -)
; => NIL

最後に、/というスペシャル変数もあります。これは、直前の評価式の返り値が多値の時に、リストにして使えるようにするものです。
(get-decoded-time)
; => 32 ;
;    17 ;
;    23 ;
;    20 ;
;    12 ;
;    2017 ;
;    2 ;
;    NIL ;
;    -9

/
; => (32 17 23 20 12 2017 2 NIL -9)

これらのスペシャル変数は処理系をインタプリタとして使う場合に利用できるため、ソースコードの中に含めるべきではありません。

第24章「システムの構築(ASDF)」

概要

ANSI Common Lispの第24章「システムの構築」を説明します。

ASDFの概要

ANSI Common Lispの第24章は「システムの構築」です。システムとはCommon Lispで構築されたプログラムやライブラリを意味しますが、実際に利用されているサードパーティ製ライブラリはほぼ全て「ASDF」というモジュール管理プログラムを介して管理されています。

Common Lispは極めて動的な性格を持っており、インタプリタで直接入力したり、loadで処理系の中に取り込まれると関数や変数が使えるようになります。パッケージによってシンボルの名前空間を拡張・継承する仕組みはありますが、それはソースファイルとは全く無関係です。Common Lispにとって対話的であることは本質であり、インタプリタを持っている、ということ以上に言語の根幹に動的な性質が組み込まれています。

このような特徴は便利といえば便利ですが、静的にライブラリをロードして使ったり、自分が書いたプログラムをライブラリとしてどこからでも呼び出せるようにしておいたりする場合もあります。そのように使うには、ライブラリのロードに関して一定の規則が必要です。

ASDFはサードパーティ製のツールですが、デファクトスタンダードになっているライブラリ管理ツールです。ASDFの規則に従ってライブラリを配置し、設計すれば、可搬性の高いプログラムになります。そしてASDFはサードパーティ製でありながら多くの処理系に含まれるため、ANSI Common Lispに標準を超えつつも、ASDFの規則に従った方がむしろ可搬性が高まる、という状況にあります。

最近ではQuicklispの登場でライブラリのダウンロードも簡単になりましたが、システムの管理自体はASDFを使用しています。このページではASDFの概要を説明します。

ASDFのロード

ASDFは多くの処理系に含まれており、含まれていない場合でもasdf.lispをダウンロードしてloadするだけで使うことができます。私は実用面では主にCCLを、このサイトの解説向けにはGNU CLISPを、確認用にSBCLをそれぞれ使っていますが、SBCLとCCLにはASDFが含まれているので以下のようにするだけでロードできます。
;; SBCL の場合
(require :asdf)
; => ("ASDF" "asdf" "UIOP" "uiop")

macOSのGNU CLISPにはASDFが含まれていないので、上記の asdf.lisp をダウンロードして、適当なディレクトリに保存します。ダウンロードしてきたら、一度コンパイルをして、ロードします。CLISPはASDFのロードが少し遅いので、以下のようにロードした状態でイメージを保存し、次からはそのイメージを直接ロードするようにすると早いです。

イメージを保存する関数は処理系によって全く異なるので、各自でマニュアルを調べてください。GNU CLISPではext:saveinitmem関数です。
;; GNU CLISP の場合
(compile-file "asdf")
;; Compiling file /Users/satoshi/lisp/asdf.lisp ...
;; Wrote file /Users/satoshi/lisp/asdf.fas
; 0 errors, 321 warnings
; => #P"/Users/satoshi/lisp/asdf.fas" ;
;    321 ;
;    321

(ext:saveinitmem "clisp-asdf.core")
;; Wrote the memory image into clisp-asdf.core (6,302,192 bytes)
; Bytes permanently allocated:            171,840
; Bytes currently in use:               6,122,976
; Bytes available until next GC:        1,528,014
; => 6122976 ;
;    1528014 ;
;    171840 ;
;    20 ;
;    30685856 ;
;    232253

.bashrc などに以下の記述を含め、シェルを再起動して clisp コマンドを打てば、ASDFロード済のCLISPが起動します。
alias clisp="clisp -M $HOME/bin/clisp-asdf.core"

GNU CLISPはC言語で書かれているため、イメージファイルの容量が少ないのが特徴です。私は色々とよく使うライブラリを盛り込んでロードして、イメージをダンプして、そのイメージをロードするようにして使っています。

ASDFのフォルダ・ファイル構造

ASDFを最もシンプルに使うには、以下の2つのルールを守ると簡単です。
  1. ホームディレクトリの common-lisp ディレクトリにライブラリを配置する
  2. プロジェクト名をライブラリディレクトリの名前と asd ファイルの名前に使う
例えば、プロジェクト名が my-project で、私のホームディレクトリが /Users/satoshi/ の場合、以下のような構造にファイル・ディレクトリを配置します。
  1. プロジェクトディレクトリ: /Users/satoshi/common-lisp/my-project/
  2. asdファイル: /Users/satoshi/common-lisp/my-project/my-project.asd
Quicklispを使うとプロジェクトディレクトリが common-lisp 直下に展開されないので注意してください。

asdファイルの書き方

ASDFは asd ファイルと呼ばれるスクリプトで管理しています。このスクリプトの中にはプロジェクトの著者やライセンス、構成物であるソースコードのリストと依存関係を記述します。

ここでは、最も広く利用されているサードパーティ製ライブラリであるAlexandriaalexandria.asd からドキュメントとテストを除いたものを引用して示します。
(defsystem "alexandria"
  :version "0.0.0"
  :licence "Public Domain / 0-clause MIT"
  :description "Alexandria is a collection of portable public domain utilities."
  :author "Nikodemus Siivola <nikodemus@sb-studio.net>, and others."
  :components
  ((:static-file "LICENCE")
   (:static-file "tests.lisp")
   (:file "package")
   (:file "definitions" :depends-on ("package"))
   (:file "binding" :depends-on ("package"))
   (:file "strings" :depends-on ("package"))
   (:file "conditions" :depends-on ("package"))
   (:file "io" :depends-on ("package" "macros" "lists" "types"))
   (:file "macros" :depends-on ("package" "strings" "symbols"))
   (:file "hash-tables" :depends-on ("package" "macros"))
   (:file "control-flow" :depends-on ("package" "definitions" "macros"))
   (:file "symbols" :depends-on ("package"))
   (:file "functions" :depends-on ("package" "symbols" "macros"))
   (:file "lists" :depends-on ("package" "functions"))
   (:file "types" :depends-on ("package" "symbols" "lists"))
   (:file "arrays" :depends-on ("package" "types"))
   (:file "sequences" :depends-on ("package" "lists" "types"))
   (:file "numbers" :depends-on ("package" "sequences"))
   (:file "features" :depends-on ("package" "control-flow"))))

おそらく、見れば意味が分かるでしょう。defsystemがASDFの主要なオペレータで、プロジェクトの構成を定義するのに用います。この関数は第1引数にプロジェクト名を取り、以下はキーワード引数です。重要なのは、:componentsキーワード引数です。この引数はリストであり、この中でファイル間の依存関係を示します。

パッケージの章でも示しましたが、defpackageマクロは package.lisppackages.lisp というファイルに単独で記載する慣例があるため、全てのファイルがパッケージファイルに依存することになります。その他のファイルは適宜必要に応じて記載します。

この asd ファイルにプロジェクトの依存関係を記載することで、依存関係の解決はASDFがやってくれることになります。

もう一つ、asd ファイルのパターンを紹介します。正規表現を行うCL-PPCREのASDF定義です。
(defsystem :cl-ppcre
  :version "2.0.11"
  :description "Perl-compatible regular expression library"
  :author "Dr. Edi Weitz"
  :license "BSD"
  :serial t
  :components ((:file "packages")
               (:file "specials")
               (:file "util")
               (:file "errors")
               (:file "charset")
               (:file "charmap")
               (:file "chartest")
               #-:use-acl-regexp2-engine
               (:file "lexer")
               #-:use-acl-regexp2-engine
               (:file "parser")
               #-:use-acl-regexp2-engine
               (:file "regex-class")
               #-:use-acl-regexp2-engine
               (:file "regex-class-util")
               #-:use-acl-regexp2-engine
               (:file "convert")
               #-:use-acl-regexp2-engine
               (:file "optimize")
               #-:use-acl-regexp2-engine
               (:file "closures")
               #-:use-acl-regexp2-engine
               (:file "repetition-closures")
               #-:use-acl-regexp2-engine
               (:file "scanner")
               (:file "api")))

先ほどのAlexandriaと異なる点が2点あります。情報を整理すると、以下のようになります。
  • defsystemの第1引数は文字列でもキーワードでも良い
  • 単純に上から読み込むことで依存関係を解決できる場合は:serialキーワード引数をtにする
AlexandriaもCL-PPCREも定番中の定番ライブラリですので、どのようなスタイルを好むかは完全に趣味の問題です。

プロジェクトのロード

common-lisp ディレクトリの中にプロジェクト毎のディレクトリが作成され、その中に適切な asd ファイルが配置されていれば、あとはプログラムのロードは一発です。
(asdf:load-system :alexandria)
; 0 errors, 0 warnings
; => T

(asdf:load-system :cl-ppcre)
; 0 errors, 0 warnings
; => T
asdf:load-system関数はプロジェクトの依存関係を解決して、コンパイルし、ロードしてくれます。コンパイルの結果は .cache などのキャッシュディレクトリに保存してくれるので、2回目以降のコンパイルは不要です。また、この関数は各環境のASDFディレクトリからロードするため、OSやASDFディレクトリの違いを吸収してくれます。

なお、処理系によってはANSI Common Lisp標準のrequire関数がこのasdf:load-system関数に置き換えられている場合もあるので、requireでもASDFのロードを行うことができる場合があります。

ライブラリ間の依存: :depends-on

ASDFの基本的な使い方の流れは以上ですが、実際のプログラムはファイル間の依存関係だけでなく、ライブラリ間の依存関係も解決しなければなりません。AlexandriaもCL-PPCREも共に単独でも用いることができるライブラリですが、例えば私がこのサイトのHTML生成に用いているmarkdown.clはいくつかのサードパーティ製ライブラリに依存しています。(註:現在はBloggerに移行したので、Markdownは使用していません。)
(asdf:defsystem markdown.cl
  :author "Andrew Danger Lyon <orthecreedence@gmail.com>"
  :license "MIT"
  :version "0.1.7"
  :description "A markdown parser for Common Lisp"
  :depends-on (#:cl-ppcre #:xmls #:split-sequence)
  :components
  ((:file "package")
   (:file "util" :depends-on ("package"))
   (:file "html" :depends-on ("util"))
   (:file "parser" :depends-on ("html"))))

:depends-onキーワード引数が使われているのが分かると思います。ここでは以下の3つのライブラリに依存しています。
  • CL-PPCRE
  • XMLS
  • split-sequence
ASDFはプロジェクト内のソースコードの依存関係だけでなく、ライブラリ間の依存関係も処理してくれます。ここでは、依存している3つのライブラリのロードを自動で行います。依存関係解決は再帰的に行われるので、もし依存ライブラリが何か別のライブラリに依存していれば、それが終わるまで再帰的にロードします。

ASDFのススメ

ASDFは標準のまま使うととてもシンプルでありながら、極めて便利な仕組みを提供してくれます。C言語に makefile があるように、Common Lispのシステム管理はASDFに任せるのがベストです。

一方、書き捨てのスクリプトなどはASDFを使用する必要はありません。そのようなものはdefpackageマクロも同一ファイルに書くか、全てのシンボルをCOMMON-LISP-USERパッケージに含めるなど、シンプルを徹底して利用することもできます。そのような場合は、compile-file関数でコンパイルしておき、load関数で1つのファイルをロードすればいいので、ASDFを使うよりも手軽です。私は、このサイトを作るのに使用しているCommon LispスクリプトはASDFを使ってライブラリのロードは行いつつも、そのスクリプトはASDFとしては定義しておらず、単一のファイルにまとめて書いています。

とはいえ、一般的にはできるだけASDFに対応してプロジェクトを記述すべきです。ASDFで定義されたものはどこからでもロードしやすく、プログラムの再利用性が高まります。他の言語と比べてもプロジェクトの管理は簡素で手軽だと思うので、ぜひ積極的に利用してください。

第23章「入力」

概要

ANSI Common Lispの第23章「入力」を説明します。

リーダーマクロ

第23章は「リーダー」、すなわち読取器についてですが、多くはプログラマでなく実装者向けの仕様なので、このページでは簡単に、今まで紹介していないリーダーマクロと一部の関数のみ紹介します。

リーダーマクロの仕組み・定義方法については第2章「構文」で紹介しました。リーダーマクロはCommon Lispにとって極めて重要な仕組みですが、自分で新しいリーダーマクロを追加すると混乱の原因になる可能性もあるので、基本的には消極的に使った方がいいでしょう。

今までに紹介していないマクロとして、bit-vectorを生成する#*と、読み込み時に評価を行う#., #+, #-を紹介します。

ビットのベクトル: #*

Common Lispで2進数表記で数を表すには、#bを使用します。
#b111
; => 7

これは正真正銘の数numberですが、数ではなくビットの列をそのままベクトルとして扱うこともできます。それがbit-vectorで、#*でリテラル表記します。
(length #*111)
; => 3

(aref #*111 2)
; => 1

読込時評価: #., #+, #-

ある種の情報は、コンパイル時でも実行時でもなく、読み込み時に評価したい場合があります。

例えば、日数を秒数に変換する場合を考えてみます。1日は24時間で、1時間は60分で、1分は60秒なので、1日の秒数を計算すると以下のようになります。
(* 60 60 24)
; => 86400

これは本質的に定数なので、日数を秒数に変換する関数は以下のように定義できます。
(defun day-to-second (n) (* n 86400))
; => DAY-TO-SECOND

しかし、86400という定数自体は、そのまま見ても意味が分かりにくいため、6024などの数をそのまま書いて定義したいとします。
(defun day-to-second (n) (* n 60 60 24))
; => DAY-TO-SECOND

ところが、この定義は先ほどの定義と比べて実行時に無駄な計算が入ります。86400は定数であるにも関わらず、毎回計算することになるからです。

このような場合に、読込時評価( read time evaluation )を行う#.マクロが役立ちます。
(defun day-to-second (n) (* n #.(* 60 60 24)))
; => DAY-TO-SECOND

#'day-to-second
; => #<FUNCTION DAY-TO-SECOND (N) 
;    (DECLARE (SYSTEM::IN-DEFUN DAY-TO-SECOND))
;    (BLOCK DAY-TO-SECOND (* N 86400))>

この#.リーダーマクロは、次に続く式を一つ読み取った時に、先行して評価します。評価結果は元の式があった場所に戻されます。そして、その値が最初からあったものとしてソースコードに組み込まれ、処理が続けられます。一度評価されると実行時には評価されないため、実行速度が遅くなることはありません。

同じように読取時評価を行いながら、条件分岐の機能を備えているのが#+#-です。これらのリーダーマクロは、次に続く式が*features*というスペシャル変数に含まれるかどうかを判断し、その次の式を評価するかどうかを分岐します。
;; GNU CLISPで試すと
#+clisp 'clisp #+sbcl 'sbcl
; => CLISP

;; SBCLで試すと
#+clisp 'clisp #+sbcl 'sbcl
; => SBCL

#+*features*に含まれる場合に2つ目の式を評価しますが、含まれない場合に評価するのが#-です。
;; CCLで試すと
#+clisp 'clisp #+sbcl 'sbcl #-(or clisp sbcl) 'something
; => SOMETHING

これらのリーダーマクロは、処理系依存を吸収するプログラムやライブラリを書く時には必須の機能です。

文字列との相互変換

一般的な入出力は第21章「ストリーム」で示した通りです。また、出力に関しては前章「出力」で紹介したformat関数が便利です。

ここでは、それ以外の関数を紹介します。

文字列から他のオブジェクトへ: read-from-string

文字列から他のオブジェクトに変換するには、read-from-string関数を使います。
(read-from-string "10")
; => 10
;    2

(read-from-string "3.14")
; => 3.14
;    4

(read-from-string "(a b c)")
; => (A B C)
;    7

(read-from-string "#(1 2 3)")
; => #(1 2 3)
;    8

(read-from-string "(+ 1 2)")
; => (+ 1 2)
;    7

(eval *)
; => 3

小数点数である文字列を'double-float型の数に変換するには、あらかじめスペシャル変数*read-default-float-format*を操作しておきます。
(type-of (read-from-string "3.14"))
; => SINGLE-FLOAT

(setq *read-default-float-format* 'double-float)
; => DOUBLE-FLOAT

(type-of (read-from-string "3.14"))
; => DOUBLE-FLOAT

他のオブジェクトから文字列へ: write-to-string

逆にオブジェクトを文字列に変換するにはwrite-to-string関数を使います。
(write-to-string 10)
; => "10"

(write-to-string 3.14)
; => "3.14"

(write-to-string '(a b c))
; => "(A B C)"

(write-to-string #(a b c))
; => "#(A B C)"

(write-to-string #'+)
; => "#<SYSTEM-FUNCTION +>"

第22章「出力(format関数)」

概要

ANSI Common Lispの第22章「出力」を説明します。

万能の出力関数: format

第22章は「プリンター」すなわち出力に関する情報が定義されています。しかし、多くは日常的なプログラミングで使うことは稀で、処理系の実装者向けに定義されています。

Common Lispには何種類も出力を行うための関数が定められていますが、中でも万能と言えるのがformat関数です。この関数はフォーマット文字列と呼ばれる独自の言語を持っています。
format関数は使いこなせれば極めて強力であり、このサイトの様々な箇所で暗黙的に使ってきました。ここでは、format関数の機能だけに特化して、丁寧に紹介していきます。ただ、仕様を網羅して説明すると分かりにくいので、機能は抜粋する代わりにサンプルを付けて紹介します。

第1引数の扱い

format関数の第1引数は destination 、すなわち「目的地」です。その値は4種類です。

意味 返り値
t 標準出力ストリーム(*standard-output*)に出力します nil
nil 文字列を返り値として返します フォーマット後の文字列
stream ストリームに出力します nil
string フィルポインタ付の文字列に出力します nil

フィルポインタ付の文字列を目的地とする場合のみ、サンプルを示します。
(defparameter *str* (make-array 0 :element-type 'base-char 
                                  :adjustable t 
                                  :fill-pointer 0))
; => *STR*

(format *str* "abc")
; => NIL

*str*
; => "abc"

基本フォーマット

この節では最も基本的なフォーマットを説明します。

文字: ~c

文字オブジェクトを表示するには~cを使用します。このフォーマットには2つの亜種が存在します。文字を読み取り可能な形(#\付)で表示する~@cと、制御文字の名前を表示する~@cです。

フォーマット 意味
~c 文字をそのまま出力します
~@c 文字を読み込み可能な形式で付で出力します
~:c 制御文字については文字の名前(char-name)を出力します
;; 文字をそのまま表示する
(format t "~c" #\newline)
; 
; => NIL

;; 文字を読み込み可能な形で表示する
(format t "~@c" #\newline)
; #\Newline
; => NIL

;; 文字の名前を表示する
(format t "~:c" #\newline)
; Newline
; => NIL

改行: ~%

改行文字は~%で出力できます。複数の改行を出力する場合、~N%を使います。
(format t "a~2%b~3%c")
; a
; 
; b
; 
; 
; c
; => NIL

チルダ: ~~

チルダはformat関数においてフォーマットに使用されるため、チルダそのものを表示する場合は2つを重ねてエスケープします。複数出力する場合は、~N~を使います。
(format t "~~")
; ~
; => NIL

(format t "~10~")
; ~~~~~~~~~~
; => NIL

汎用: ~a, ~s

format関数のフォーマットは非常に多様ですが、特に複雑な処理を行うわけではなく、「いい感じ」で処理したい時は~a~sが便利です。
;; 数は同じ
(format t "~a ~s" 10 10)
; 10 10
; => NIL

;; ~a は「いい感じ」、~s は読み込み可能
(format t "~a ~s" #\a #\a)
; a #\a
; => NIL

(format t "~a ~s" "abc" "abc")
; abc "abc"
; => NIL
~sは多くの場合、再度読み込みが可能な形式でオブジェクトを表示します。~aは一般的に人間が読んで分かりやすいように表示します。

これらの汎用フォーマットはいくつかのオプションが使えます。その中でもよく使う3種類を紹介します。~a~sで共通して使えるので、~aのみで示します。

フォーマット 意味
~Na N 文字に満たない場合、空白で埋めます
~N,,,'Ca 空白ではなく文字 C で埋めます
~N@a 左ではなく右に配置します
~:a nil()と表示します
;; 数をつけると空白で埋める
(format t "~5a ~5a" #\a #\b)
; a     b    
; => NIL

;; 埋める文字を指定することができる
(format t "~5,,,'0a ~5,,,'Xa" #\a #\b)
; a0000 bXXXX
; => NIL

;; @ を付けると右寄せになる
(format t "~5,,,'0@a ~5,,,'X@a" #\a #\b)
; 0000a XXXXb
; => NIL

;; : を付けると NIL が () になる
(format t "~a ~:a" nil nil)
; NIL ()
; => NIL

整数フォーマット

この節では、整数のフォーマットを紹介します。

整数は表示したい進数によってフォーマットの種類が異なりますが、オプションは基本的にどれも同じです。

10進数: ~d

整数を10進数で表示したい場合は~dを使います。この~dをはじめとして、整数を表示するフォーマットには共通したオプションが存在しますので、紹介します。

フォーマット 意味
~Nd N 文字に満たない場合、空白で埋めます
~N,'Cd 空白ではなく文字 C で埋めます
~:d 3桁毎に区切って表示します
~@:d +または-の符号を必ず表示します
(format t "~d" 10000)
; 10000
; => NIL

;; 空白で埋める
(format t "~10d" 10000)
;      10000
; => NIL

;; 埋める文字を指定する
(format t "~10,'Xd" 10000)
; XXXXX10000
; => NIL

;; 符号を必ず表示する
(format t "~@d" 10000)
; +10000
; => NIL

;; カンマで区切る
(format t "~:d" 10000)
; 10,000
; => NIL

2進数: ~b

format関数は整数の進数を変換することができ、~bを指定すると2進数になります。
2進数で表示する場合、0埋めのフォーマットと区切りのフォーマットが役に立ちます。

フォーマット 意味
~4,'0b 4bit単位で表示し、0で埋めます
~9,'0,' ,4:b 8bit(1オクテット)の数を4bit単位で表示し、0で埋めます
(format t "~b" 5)
; 101
; => NIL

;; 4bit 単位で表示する
(format t "~4,'0b" 5)
; 0101
; => NIL

;; 4bit 単位で表示すると間にスペースが入るので、全部で9文字
;; オプションは
;;   全部で9文字
;;   0で埋める
;;   スペースで区切る
;;   4桁で区切る
;;   区切りを入れる(:)
;; という順番
(format t "~9,'0,' ,4:b" 125)
; 0111 1101
; => NIL

2進数を綺麗に表示するのはformat関数の得意とするところです。このような何気ない処理も、自分でプログラムしようと思えば結構難しいのです。

8進数: ~o

8進数は3bitに対応するので、あまり使うことはないかもしれませんが、私はUnixのパーミッションを調べるときにサクッと使ったりします。整数を8進数で表示するには~oフォーマットを使います。
(format t "~o" #b111101101)
; 755
; => NIL

ファイルのパーミッションは所有者・グループ・その他という順番で、それぞれ読み・書き・実行というビットで構成されるので、全ての権限を与える場合は#b111で、読みと実行だけを与える場合は#b101です。これを並べると上の例の#b111101101となり、これを8進数に変換すると755になります。

16進数: ~x

16進数は4bitに対応しており、16進数を2つ並べることで1オクテットを表現できるので、頻繁に利用されます。

16進数を表示するには~xを使いますが、16進数は一般に2文字並べて表示するため、以下のような使い方が便利です。

フォーマット 意味
~2,'0x 8bit(1オクテット)の数を常に2文字で表示します
~5,'0,' ,2:x 16bit(2オクテット)の数を2文字区切りで表示します
(format t "~x" 255)
; FF
; => NIL

;; 8bit(= 15)以下の数も2文字で表示する
(format t "~2,'0x" 10)
; 0A
; => NIL

;; 9bit以上16bit以下の数を2文字区切りで表示する
(format t "~5,'0,' ,2:x" 12345)
; 30 39
; => NIL

N進数・英語・ローマ数字: ~r

整数に関して最後に紹介する~rフォーマットは、5種類の使い方があります。

フォーマット 意味
~Nr N 進数で表示します
~r 英語で数を表示します
~:r 英語の序数で数を表示します
~@r ローマ数字で数を表示します
~:@r 旧式のローマ数字で数を表示します
;; N進数
(format t "~2r" 9)
; 1001
; => NIL

;; 英語
(format t "~r" 9)
; nine
; => NIL

;; 英語の序数(-th)
(format t "~:r" 9)
; ninth
; => NIL

;; ローマ数字
(format t "~@r" 9)
; IX
; => NIL

;; 旧式のローマ数字
(format t "~:@r" 9)
; VIIII
; => NIL

10くらいまでの数しか表示できないのではないか、と思った方はCommon Lispの力を過小評価しているでしょう。
(format t "~r" 123)
; one hundred and twenty-three
; => NIL

(format t "~r" 123456789)
; one hundred and twenty-three million, 
; four hundred and fifty-six thousand, 
; seven hundred and eighty-nine
; => NIL

小数点数フォーマット

この節では小数点を含む数のフォーマットについて説明します。

小数: ~f, ~$

小数点数をシンプルに表示するには~fを使用します。小数点数のフォーマットで最も利用するのは、小数点第 N 位まで表示する、というコントロールだと思います。

フォーマット 意味
~,Nf 小数点第 N 位まで表示します
~@f 符号を必ず表示します
(format t "~f" 3.1415)
; 3.1415
; => NIL

;; 小数点第 2 位まで表示する
(format t "~,2f" 3.1415)
; 3.14
; => NIL

;; 符号を必ず表示する
(format t "~@f" 3.1415)
; +3.1415
; => NIL

ただし、小数点第2位まで表示する場合は、~$を使うことができます。
(format t "~$" 3.1415)
; 3.14
; => NIL

指数: ~e

大きな数などの場合は指数で表示するのが便利な場合もあります。指数で表示するには~eを使います。オプションは~fと同じです。
(format t "~e" 3.1415)
; 3.1415E+0
; => NIL

;; 小数点第 2 位まで表示する
(format t "~,2e" 3.1415)
; 3.14E+0
; => NIL

;; 10^3 = 1000
(format t "~,2e" 3141.5)
; 3.14E+3
; => NIL

;; 符号を必ず表示する
(format t "~,2@e" 3141.5)
; +3.14E+3
; => NIL

レイアウトフォーマット

この節では表示のレイアウトに関するフォーマットを紹介します。

空白: ~t

空白を複数出力するには~tフォーマットが便利です。このフォーマットはシンプルに使うとただスベースを1文字出力するだけなので、そのままではほとんど使いません。おそらく、~N@tという指定が一番便利なのではないでしょうか。

フォーマット 意味
~Nt 行頭から数えて N 個の空白を出力する
~N@f 手前の出力結果から数えて N 個の空白を出力する
;; シンプルに使用した場合、1文字の空白を出力する
(format t "~tA~tB~tC")
;  A B C
; => NIL

;; N を指定すると行頭から数えて空白を出力する
;; 2回目も行頭から数えるので、指定は意味がなくなる
(format t "~5tA~5tB~5tC")
;      A B C
; => NIL

;; @ をつけると手前の出力結果から数える
(format t "~5@tA~5@tB~5@tC")
;      A     B     C
; => NIL

等間隔: ~<, ~>, ~;

文字列を等間隔で出力するには、~<~>を組み合わせます。これらは文字幅を指定しなければ意味がないので、必ず文字幅を指定します。また、これらの中で用いる区切りフォーマットが~;です。

フォーマット 意味
~N<全部で N 文字の幅の中で左端と右端に文字列を配置し、間を等間隔のスペースで区切ります
~N:<全部で N 文字の幅の中で右に詰めて文字列を配置し、左側も含めて等間隔のスペースで区切ります
~N@<全部で N 文字の幅の中で左に詰めて文字列を配置し、右側も含めて等間隔のスペースで区切ります

;; 左右に詰めて文字列を配置するので、区切りスペースは2つ
;; 全部で12文字のうち、文字6文字を除く6文字を2つのスペースに分けるので、
;;   3スペース + 3スペース
;; になる
(format t "~12<ab~;cd~;ef~>")
; ab   cd   ef
; => NIL

;; 右寄せにして左側にも空白を配置するので、
;;   2スペース + 2スペース + 2スペース
;; になる
(format t "~12:<ab~;cd~;ef~>")
;   ab  cd  ef
; => NIL

;; 左寄せにして右側にも空白を配置するので、
;;   2スペース + 2スペース + 2スペース
;; になる
(format t "~12@<ab~;cd~;ef~>")
; ab  cd  ef  ; ここまでスペースが出力されている
; => NIL

制御フォーマット

前小節で紹介したレイアウトフォーマットは制御に関わるものですが、Common Lispのformat関数はもっと様々な制御を行うことができます。ここでは、いくつかの制御フォーマットを紹介します。

消費: ~*

最も原始的な制御フォーマットは~*です。このフォーマットもオプションによって動作が大きく異なります。

フォーマット 意味
~* 1個分の引数を何もせずに消費します
~N* N 個分の引数を何もせずに消費します
~N:* N 個分の引数を戻して、次に進みます
~@* 最初の引数を戻して、次に進みます
~N@* N 番目の引数を取り出して、次に進みます
;; シンプルに使うと、1つの引数を消費して何もしない
(format t "~a ~* ~a" 1 2 3)
; 1  3
; => NIL

;; N を指定すると、N個の引数を消費する
(format t "~a ~2* ~a" 1 2 3 4)
; 1  4
; => NIL

;; : を付けると消費した引数を元に戻す
(format t "~a ~:* ~a" 1)
; 1  1
; => NIL
(format t "~a ~a ~a ~3:* ~a ~a ~a" 1 2 3)
; 1 2 3  1 2 3
; => NIL

;; @ を付けると、指定した引数にジャンプして取り出してくる
(format t "~a ~@* ~a" 1 2 3)
; 1  1
; => NIL
(format t "~a ~1@* ~a" 1 2 3)
; 1  2
; => NIL

条件: ~[ ~]

format関数には簡単な条件分岐を行うフォーマット文字列が用意されています。それが~[~]です。

このフォーマット文字列もオプションによって大きく動作が異なります。

フォーマット 意味
~[N0~;N1~;N2~] 引数をインデックスとして N 番目の要素を表示します
~[N0~;N1~;N2~:;Otherwise~] 引数に対応する要素が存在しない場合、Otherwiseを表示します
~:[FALSE~;TRUE~] 引数の真偽に応じて表示する要素を変えます
~@[TRUE~] 引数が真の場合、引数を消費せず要素を表示します。偽の場合、要素は表示しませんが引数は消費します
~@[TRUE~*~] 引数が真の場合のみ表示します。真でも偽でも引数は消費されます
;; 引数をインデックスに見立て、対応する要素を表示する
(format t "~[none~;one~;two~;three~]" 0)
; none
; => NIL
(format t "~[none~;one~;two~;three~]" 2)
; two
; => NIL

;; 引数に対応する要素がない場合、~:; の直後の要素を表示する
(format t "~[none~;one~;two~;three~:;something~]" 10)
; something
; => NIL

;; 真偽に対応する要素を表示する
(format t "~:[odd~;even~]" (evenp 11))
; odd
; => NIL
(format t "~:[odd~;even~]" (evenp 12))
; even
; => NIL
~@[フォーマットはそのまま使うと、引数を消費するかどうかが真偽で異なります。先に、~*を使って真の場合も消費するようにしたパターンを示します。一般的には~a~sなど、引数を消費するフォーマットを入れるか、引数の値を使わないなら~*で消費するようにした方が普通の使い方には合っていると思います。
(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" t t)
; Opt1-ON Opt2-ON
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" t nil)
; Opt1-ON 
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" nil t)
;  Opt2-ON
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" nil nil)
; 
; => NIL

もし引数を消費するようにコントロールしなければ、1つ目がtの場合にその引数が残るので、2つ目の条件分岐に失敗します。
(format t "~@[Opt1-ON~]~@[ Opt2-ON~]" t nil)
; Opt1-ON Opt2-ON
; => NIL

反復: ~{, ~}

反復は最も便利なフォーマットの一つで、このサイトの他のページでも頻繁に利用してきました。反復を行う~{フォーマットも、オプションによって動作が異なります。

フォーマット 意味
~{LOOP~} 引数(リスト)の要素に対して反復的に表示します
~:{LOOP~} 引数(リストのリスト)の要素に対して反復的に表示します
~@{LOOP~} 以降の引数を可変長引数と同様にリストにして反復的に表示します
~:@{LOOP~} 以降の引数(リスト)をリストのリストにして反復的に表示します
;; シンプルに使う場合はリストの要素に対して反復する
(format t "~{~a, ~}" '(1 2 3))
; 1, 2, 3, 
; => NIL

;; ~:{ はリストのリストを分解して処理できる
(format t "~:{~a, ~a, ~a~%~}" '((1 2 3) (4 5 6) (7 8 9)))
; 1, 2, 3
; 4, 5, 6
; 7, 8, 9
; => NIL

;; ~@{ は引数を可変長引数のようにリストと見なして処理できる
(format t "~@{~a, ~}" 1 2 3)
; 1, 2, 3, 
; => NIL

;; ~:@{ はリストを可変長引数のように扱いながら分解して処理できる
(format t "~:@{~a, ~a, ~a~%~}" '(1 2 3) '(4 5 6) '(7 8 9))
; 1, 2, 3
; 4, 5, 6
; 7, 8, 9
; => NIL

なお、繰り返しでは中断を行う~^を合わせて使うと便利です。中断は、最後の要素を処理したところで処理を止めることができるので、上の例の最後のカンマの出力を止めることができます。
(format t "~{~a~^, ~}" '(1 2 3))
; 1, 2, 3
; => NIL

再帰: ~?

フォーマット文字列自体を引数として消費するフォーマットも存在します。~?です。

このフォーマットは、見た目はシンプルですが動作は複雑です。~?は2つの引数を消費しますが、その形式は決まっており、一つ目がフォーマット文字列、二つ目がリストです。リストを引数に見立ててフォーマット文字列でフォーマットした文字列が、~?の部分に使われます。
(format t "~?" "(~a, ~a)" '(1 2))
; (1, 2)
; => NIL

(format t "~?" "(~a, ~a, ~a)" '(1 2 3))
; (1, 2, 3)
; => NIL

フォーマット文字列自体をデータとしてやりとりしたい場合は便利です。

中断: ~^

反復フォーマットの中でも紹介した通り、~^は最後の要素を消費した段階で反復を中断できます。
(format t "~{~a~^, ~}" '(1 2 3))
; 1, 2, 3
; => NIL

その他のフォーマット

あまり使うことはないかもしれませんが、こんなのもあるよ、ということであと少しだけ紹介します。

大文字・小文字: ~(, ~)

大文字と小文字を変換するには~(~)を使用します。このフォーマットは、小文字と大文字の相互変換だけでなく、文頭の大文字化やワードの大文字化にも対応しています。

フォーマット 意味
~(SENTENCE~) 文字列を全て小文字に変換します
~:@(sentence~) 文字列を全て大文字に変換します
~:(sentence~) 文字列に含まれるワードの最初の文字を全て大文字にします
~@(sentence~) 文字列の最初の文字を大文字にします
;; 何もオプションを付けないと小文字に変換する
(format t "~(~a~)" "THIS IS A PEN.")
; this is a pen.
; => NIL

;; ~:@( を付けると大文字に変換する
(format t "~:@(~a~)" "this is apen.")
; THIS IS APEN.
; => NIL

;; ~:( を付けると文中のワードの先頭の文字だけを大文字にする
;; 英文のタイトルはこの形式で表現される
(format t "~:(~a~)" "THIS IS A PEN.")
; This Is A Pen.
; => NIL

;; ~@( を付けると文頭の文字だけを大文字にする
;; 英文の本文はこの形式で表現される
(format t "~@(~a~)" "THIS IS A PEN.")
; This is a pen.
; => NIL

単数形・複数形: ~p

私は日本語圏で生活しているのであまりありがたみはないですが、英語圏でプログラミングしているなら~pフォーマットは地味に便利かもしれません。
~pフォーマットは単数形と複数形の表記を使い分けることができます。

フォーマット 1に等しい場合 1に等しくない場合
~p "" "s"
~@p "y" "ies"
;; 共に単数形
(format t "bo~@p and girl~p" 1 1)
; boy and girl
; => NIL

;; ~p のみ複数形 ("s"を出力)
(format t "bo~@p and girl~p" 1 2)
; boy and girls
; => NIL

;; ~@p も複数形 ("ies"を出力)
(format t "bo~@p and girl~p" 2 2)
; boies and girls
; => NIL