Common Lispの型宣言

この記事はLisp Advent Calendar 2016の24日目の記事です。

年末に入りとうとうクリスマスイブですが皆様いかがお過ごしでしょうか。

Common Lispは動的型付けの言語ですが、最適化のための型宣言機構が規格に組み込まれています。
また、SBCLやCMUCLでは、この型宣言を利用し、型エラーをコンパイル時に指摘する機能がついています。

型宣言と最適化の関係については、

* [最適化 - Common LISP users jp](http://cl.cddddr.org/index.cgi?%E6%9C%80%E9%81%A9%E5%8C%96)
* [Cより高速なCommon Lispコードを書く](http://blog.8arrow.org/entry/2014/10/23/063044)
* [Common Lispの最適化に挑戦してみる](http://phaendal.hatenablog.com/entry/2015/07/07/054205)

これらの記事によくまとまっています。

この記事は、実際にどのような記法で型宣言をするのかについて、学習のためにまとめたものになります。

## type specifier
型は _type specifier (typespec)_ という方式で記述されます。
typespecは、シンボル、クラス、リストのいずれかで表現されます。

```cl
;; シンボル
CL-USERS> (typep 42 'integer)
T
CL-USERS> (typep 'foobar 'symbol)
T
CL-USERS> (typep #(1 2 3) 'vector)
T
;; リスト
CL-USERS> (typep 42 '(integer 0 10)) ; (integer [下限 [上限]])
NIL
CL-USERS> (typep (cons "answer" 42) '(cons string integer))
T
;; クラス
CL-USERS (setf hoge-class-obj (defclass hoge () ()))
#<STANDARD-CLASS COMMON-LISP-USER::HOGE>
CL-USERS (typep (make-instance 'hoge) hoge-class-obj)
T
CL-USERS (typep (make-instance 'hoge) 'hoge) ; もちろんシンボルでも良い
T
```

標準で組み込まれているtypespecの一覧は[CLHS: Section 4.2.3](http://clhs.lisp.se/Body/04_bc.htm)にあります。

## supertype, subtype
型には継承関係が存在します。たとえば、numberはfixnumのsupertypeです。
すべての型のsupertypeに君臨するのはt型[^1]です。
[^1]: おそらく'top'の頭文字
また、t型のsupertypeはt型になっています。

Rubyなどのオブジェクト指向言語のクラスの継承関係とまったく同じです。

HyperSpecには型の継承関係も載っているので、必要があれば参照すると良いでしょう。

## 型宣言
型は、(declare (type [typespec] [変数名]))、(declaim (ftype [typespec] [関数名]))などで行います。

以下のコードがその例です。

```cl
;; 関数の型宣言
(declaim (ftype (function (fixnum) (values fixnum &optional)) fib))
(defun fib (n)
  (declare (type fixnum n)) ; 変数の型宣言
  (the fixnum               ; 任意のフォームの型宣言
       (if (> 2 n)
           1
           (+ (fib (- n 1))
              (fib (- n 2))))))
```

## ftype(関数型)
関数の型は、基本的に`(function ([引数の型]) (values [戻り値の型] &optional))`の形になっています。

戻り値がvaluesになっていて、&optionalが最後についていることで、multiple-values-callなどの多値関数を取る関数に渡すことができます。

## deftype defstruct defclass define-condition
新たな型を定義する方法は大きく分けて2つあります。
1つ目はdeftypeにより定義する方法。
2つ目は新たなクラスを定義する方法です。

以下はdeftypeでの型定義の例です。

```cl
CL-USER> (deftype uint () '(integer 0))
UINT
CL-USER> (typep 23 'uint)
T
CL-USER> (typep -23 'uint)
NIL
CL-USER> (deftype maybe (type) `(or null ,type))
MAYBE
CL-USER> (typep 42 '(maybe (eql 42))
T
CL-USER> (typep nil '(maybe fixnum))
T
```

クラスはdefclass、defstruct、define-conditionで定義できます。
クラス名が型の名前になるので、hogeクラスのインスタンスの型はhogeになります。

## SBCLの型チェック
[SBCL User Manual - Handling of Types](http://www.sbcl.org/manual/#Handling-of-Types)

SBCLでは、関数にftypeで型宣言をつけておくことで、コンパイル時に型エラーを指摘する機能がついています。

例えば、さきほどのfib関数を文字列で呼び出すと、

```cl
CL-USER> (defun test () (fib "10"))
; file: /private/var/tmp/tmp.9lIEXK
     ; in: DEFUN TEST
     ;     (FIB "10")
     ;
     ; note: deleting unreachable code
     ;
     ; caught WARNING:
     ;   Constant "10" conflicts with its asserted type FIXNUM.
     ;   See also:
     ;     The SBCL Manual, Node "Handling of Types"
     ;
     ; compilation unit finished
     ;   caught 1 WARNING condition
     ;   printed 1 note
```
このように警告が表示されます。

## 参考URL
* https://ja.wikipedia.org/wiki/Common_Lisp
* http://cl.cddddr.org/index.cgi?%E6%9C%80%E9%81%A9%E5%8C%96
* http://clhs.lisp.se/Front/index.htm
* http://www.slideshare.net/toyozumiKouichi/common-lisp-32559118
* http://stackoverflow.com/questions/19485248/what-do-optional-and-rest-mean-in-a-values-type-specifier
* http://www.sbcl.org/manual/#Handling-of-Types
* http://stackoverflow.com/questions/21064113/how-to-specify-element-type-in-vector-of-sbcl-or-common-lisp
* http://www.fireproject.jp/feature/common-lisp/details/multiple-value.html