let-plus: 汎用束縛系マクロ

概要

このページでは、定番の汎用束縛系マクロであるlet-plusを紹介します。

let-plusAlexandriaanaphoraに依存しています。各パッケージをASDFまたはQuicklispでロード可能な状態にしてから利用してください。また、let-plusは一般に全てのシンボルを継承して使われることが多いため、use-packageまたはdefpackage:useで継承して使用してください。

以下ではASDFを用いてlet-plusを使用可能な状態にするサンプルを示します。
(asdf:load-system :let-plus)
; => T

(use-package :let-plus)
; => T

let+の使い方一覧

let-plusパッケージをロードすると、let+マクロを使用可能になります。これはANSI Common Lispで定められた様々な束縛系スペシャルオペレータ・マクロを一つのオペレータだけで実現できる高機能なマクロです。

代表的な使い方を以下に列挙し、各節で使い方のサンプルを示します。
  1. シンプルな束縛
  2. リストの分配束縛
  3. ラムダリストの束縛
  4. インスタンス(スロット・アクセッサ)の束縛
  5. 構造体の束縛
  6. 多値の束縛
  7. 配列の束縛(分配束縛・指定束縛)
  8. 関数の束縛
  9. 属性リストの束縛
  10. ハッシュテーブルの束縛
  11. 複素数の束縛
let-plusパッケージにはlet+以外にもdefun+というマクロも用意されていますが、これはlet+の機能をdefunに対応させたものです。ここではlet+だけを例にとって紹介します。

1. シンプルな束縛

let+は特に気にしなければlet*と同じように使うことができます。
(let+ ((a 1) (b 2) (c (+ a b))) (list a b c))
; => (1 2 3)

初期値を指定しない場合、nilが用いられます。
(let+ (a b c) (list a b c))
; => (NIL NIL NIL)

2. リストの分配束縛

let+にはリストのパターンマッチによる分配機能が付いており、destructuring-bindのように使うことができます。
(let+ (((a b c) '(1 2 3))) (vector a b c))
; => #(1 2 3)

便利な機能として、シンボル&ignを使うとその部分にマッチした値は無視されます。
(let+ (((a &ign c) '(1 2 3))) (vector a c c))
; => #(1 3 3)

この&ignシンボルは他の様々な機能でも汎用的に使用できます。

3. ラムダリストの束縛

ラムダリストとは関数の引数の並びのことで、Common Lispではシンプルな引数の並び以外にも&optionalシンボルを用いたオプショナル引数や&keyシンボルを用いたキーワード引数などを使用することができます。この機能は「ラムダリスト」が使用可能と定められているdefunlambdaなど特定のオペレータでしか使うことができませんが、let+ではそれを一般の束縛に拡張して使うことができます。

まずは&optionalによるオプショナル引数です。
(let+ (((a &optional (b 2)) '(1))) (vector a b))
; => #(1 2)

(let+ (((a &optional (b 2)) '(1 3))) (vector a b))
; => #(1 3)

次に、&keyによるキーワード引数の例です。
(let+ (((a &optional (b 2) &key (c 3)) '(1))) (vector a b c))
; => #(1 2 3)

(let+ (((a &optional (b 2) &key (c 3)) '(1 20))) (vector a b c))
; => #(1 20 3)

(let+ (((a &optional (b 2) &key (c 3)) '(1 20 :c 30))) (vector a b c))
; => #(1 20 30)

キーワード引数はANSI標準と同様に、指定されたかどうかをチェックする機能もあります。
(let+ (((a &optional (b 2) &key (c 3 c?)) '(1 20))) (vector a b c c?))
; => #(1 20 3 NIL)

4. インスタンス(スロット・アクセッサ)の束縛

let+にはクラスのインスタンスを束縛する機能もあります。ANSI標準のwith-slotsマクロやwith-accessorsマクロと同じです。インスタンスの束縛には&slotsシンボルまたは&accessorsシンボルを使用します。

まず、サンプルとなるクラスを定義し、そのクラスを使ってインスタンスを生成します。
(defclass date ()
  ((year :initarg :year
         :accessor year-of)
   (month :initarg :month
          :accessor month-of)
   (date :initarg :date
         :accessor date-of)))
; => #<STANDARD-CLASS DATE>

(defparameter *today* (make-instance 'date :year 2018 :month 2 :date 4))
; => *today*
*date*
; => #<DATE #x302000E3A52D>

スロットの名前で束縛するときは&slotsシンボルを使います。
(let+ (((&slots year month date) *today*))
  (format nil "~a/~a/~a" year month date))
; => "2018/2/4"

スロットの名前は別名のエイリアスで利用することも可能です。
(let+ (((&slots (yy year) (mm month) (dd date)) *today*))
  (format nil "~a/~a/~a" yy mm dd))
; => "2018/2/4"

アクセッサで束縛するときは、&accessorsシンボルを使います。
(let+ (((&accessors (yy year-of) (mm month-of) (dd date-of)) *today*))
  (format nil "~a/~a/~a" yy mm dd))
; => "2018/2/4"

5. 構造体の束縛

クラスのインスタンスに対する束縛と同様に、let-plusは構造体に対する束縛も可能です。構造体の場合は&structureシンボルを使うとともに、直後に構造体のスロットへのアクセッサの命名規則を配置します。構造体はデフォルトで「構造体名+ハイフン」がアクセッサの命名規則であるため、これを配置します。

以下がサンプルです。

;; 構造体の定義
(defstruct date
  year month date)
; => DATE

;; 構造体の実体の生成
(defparameter *today* (make-date :year 2018 :month 2 :date 4))
; => *TODAY*

;; 確認
*today*
; => #S(DATE :YEAR 2018 :MONTH 2 :DATE 4)

;; let+ の &structure による束縛
(let+ (((&structure date- year month date) *today*))
  (format nil "~a/~a/~a" year month date))
; => "2018/2/4"

6. 多値の束縛

let+はANSI標準のmultiple-value-bindの代わりにもなるため、多値の束縛も可能です。

多値の束縛には&valuesシンボルを使います。
(let+ (((&values a b) (floor 5 2)))
  (format nil "~a ... ~a" a b))
; => "2 ... 1"

floorは多値を返す典型的な変数で1つ目の返り値が商、2つ目の返り値が余りです。剰余の扱いについてはANSI Common Lispの標準で様々なオペレータが定めらていますので、気になる方は仕様入門の第12章「」を参照してください。

7. 配列の束縛

配列の束縛は主に2通りの方法があります。

まずは「分配束縛」です。こちらはリストの束縛と同様にマッチングに基づいて値と変数が紐づけられます。
(let+ ((#(a b c d e) #(1 2 3 4 5)))
  (format nil "~{~a~^, ~}" (list a b c d e)))
; => "1, 2, 3, 4, 5"

もちろん、配列一般に対応しているため、ベクトル以外の多次元配列も束縛可能です。
(let+ ((#2a((a b)(c d)) #2a((1 2)(3 4))))
  (list a b c d))
; => (1 2 3 4)

もう一つの方法は配列のインデックスを指定する束縛です。こちらはマッチングではないので、配列の一部を束縛することができます。インデックス指定型の束縛を使うには&array-elementsシンボルを使います。
(let+ (((&array-elements (a 0 1)(b 1 0)) #2a((1 2)(3 4))))
  (vector a b))
; => #(2 3)

もちろん、&ignシンボルを使うこともできるため、小さい配列なら&ignを使って分配束縛を行うのも手です。
(let+ ((#2a((&ign a)(b &ign)) #2a((1 2)(3 4))))
  (vector a b))
; => #(2 3)

8. 関数の束縛

関数の束縛は&flet&labelsを使うことができます。両者の違いは再帰に対応しているか否かという点だけです。再帰を使わない場合は&fletを、再帰を使う場合は&labelsを使用します。

(let+ (((&flet double (x) (* x 2))))
  (double 10))
; => 20

9. 属性リストの束縛

let+&plistシンボルを使うと属性リストを便利に使うことができます。
(let+ (((&plist a b) '(a 1 b 2)))
  (vector a b))
; => #(1 2)

10. ハッシュテーブルの束縛

let+&hash-tableシンボルを使うと属性リストと同様にハッシュテーブルを使うことができます。
(defparameter *ht* (make-hash-table))
; => *HT*

(setf (gethash 'a *ht*) 1)
; => 1
(setf (gethash 'b *ht*) 2)
; => 2

(let+ (((&hash-table a b) *ht*))
  (vector a b))
; => #(1 2)

11. 複素数の束縛

メインで使うことはあまりないのではないかと思いますが、let+&complexシンボルで複素数の実部と虚部の束縛に使うことができます。
(let+ (((&complex a b) #c(2 3)))
  (vector a b))
; => #(2 3)

0 件のコメント :

コメントを投稿