とほほのLISP入門

トップ > とほほのLISP入門

目次

LISPとは

インストール

Ubuntu 20.04 に SBCL をインストールする例を示します。最新版は http://www.sbcl.org/platform-table.html にあります。

$ sudo apt install -y wget bzip2 make
$ wget http://prdownloads.sourceforge.net/sbcl/sbcl-2.2.1-x86-64-linux-binary.tar.bz2
$ bzip2 -cd sbcl-2.2.1-x86-64-linux-binary.tar.bz2 | tar xvf -
$ cd sbcl-2.2.1-x86-64-linux
$ sudo sh install.sh
$ sbcl --version
SBCL 2.2.1

Hello world

REPL (Read-Eval-Print Loop)

まず、sbcl コマンドで REPL を起動します。プロンプト * が表示されたら (write-line ...) を入力します。2回表示されるのは、(write-line ...) で Hello world! を書き出したあと、(write-line ...) 文自体の戻り値として "Hello world!" を返し、それを表示しているからです。(quit) または Ctrl-D を入力することで REPL を抜けます。

$ sbcl
* (write-line "Hello world!")
Hello world!
"Hello world!"
* (quit)      ; または Ctrl-D 

SBCL では Ctrl-p や Ctrl-n などのヒストリや行編集機能が使用できませんが、rlwrap でラッピングすることによりヒストリ機能が使用できるようになります。

$ sudo apt install -y rlwrap
$ rlwrap sbcl
* (write-line "Hello world!")
* (Ctrl-p)                     ; Ctrl キーを押しながら p を押す
* (write-line "Hello world!")  ; ヒストリでひとつ前の入力を呼び出せる
スクリプト実行

スクリプトファイルとして実行するには、下記の内容を hello.lisp という名前で作成してください。

(write-line "Hello world!")

これを下記の様に実行してください。

$ sbcl --script hello.lisp
Hello world!
FASLファイル

SBCL から *.lisp を *.fasl ファイルにコンパイルすることで実行を高速化することができます。

$ sbcl
* (compile-file "./hello.lisp")
* (quit)
$ chmod 755 ./hello.fasl
$ ./hello.fasl
Hello world!
実行ファイル

実行コマンドにコンパイルするのはちょっと面倒で、まず hello.lisp を下記の様に変更します。

(defun main ()
  (write-line "Hello world!"))

(sb-ext:save-lisp-and-die "hello"
                          :toplevel #'main
                          :executable t)

これを sbcl コマンドでコンパイルすると、実行ファイル hello ができます。サイズは約36MBになります。でかい...

$ sbcl --noinform --no-sysinit --no-userinit --load hello.lisp
$ ./hello
Hello world!

コメント

; から行末、#| から |# まではコメントとみなされ、プログラムの実行上無視されます。

; コメント....

#|
  コメント...
  コメント...
|#

S式

Lisp のプログラムは複数の S式(S-expression) から構成されます。S式には、式やリストや文字列や数値などを指定します。

(write-line "ABC")     ; 式
'(1 2 3)               ; リスト
"ABC"                  ; 文字列
12.3                   ; 数値

Lispで扱える型

実装によって多少の差異がありますが、Lisp では下記のような階層を持つ型が定義されています。

t                           ; すべての型の親
+ sequence                  ; シーケンス
| + vector                  ; ベクトル(arrayのサブタイプでもある)
| + list                    ; リスト
|   + null                  ; NIL(symbolのサブタイプでもある)
|   + cons                  ; コンスセル
+ (atom)                    ; アトム
  + symbol                  ; シンボル
  | + null                  ; NIL(listのサブタイプでもある)
  + stream                  ; ストリーム
  + structure-object        ; 構造オブジェクト
  | + package               ; パッケージ
  + pathname                ; パス名
  + random-state            ; ランダムステート
  + readtable               ; リードテーブル
  + function                ; 関数
  | + compiled-function     ; コンパイルされた関数
  + array                   ; 配列
  | + simple-array          ; シンプル配列
  | + vector                ; ベクトル(sequenceのサブタイプでもある)
  |   + simple-vector       ; シンプルベクトル
  |   + bit-vector          ; ビットベクトル
  |   | + simple-bit-vector ; シンプルビットベクトル(simple-vectorのサブタイプでもある)
  |   + string              ; 文字列
  |     + simple-string     ; シンプル文字列
  + hash-table              ; ハッシュテーブル
  + character               ; 文字
  | + standard-char         ; 標準文字
  + number                  ; 数値
    + real                  ; 実数
    | + rational            ; 有理数
    | | + integer           ; 整数
    | | | + fixnum          ; 整数(有限桁)
    | | | | + bit           ; ビット(*-byte のサブタイプでもある)
    | | | + bignum          ; 整数(無限桁)
    | | | + signed-byte     ; サインバイト
    | | | + unsigned-byte   ; アンサインバイト
    | | + ratio             ; 分数
    | + float               ; 小数
    |   + single-float      ; 単精度小数
    |   + double-float      ; 倍精度小数
    + complex               ; 複素数

出力

文字列出力(write, write-line)

write は文字列にも数値にも使用できますが改行しません。文字列には "..." がつきます。write-line は "..." 無しで改行付きで出力しますが、文字列しか出力できません。

(write "ABC")          ;=> 改行なしで "ABC"
(write 123)            ;=> 改行なしで 123 
(write-line "ABC")     ;=> ABC のあとに改行
(write-line 123)       ; 文字列以外はエラー
文字列・数字出力(print, princ, prin1)

print, prin1, princ は文字列や数値を書き出すことができます。print は行末ではなく、行頭に改行を入れます。

(print "ABC")          ;=> 改行のあとに "ABC"
(prin1 "ABC")          ;=> 改行なしで "ABC"
(princ "ABC")          ;=> 改行なしで ABC
改行出力(terpri)

terpri は改行を出力します。

(princ "ABC")(terpri)  ;=> ABC の後に改行
フォーマット(format)

文字列を出力するには下記の様にします。~% は改行文字を意味します。

(format t "Hello world!~%") ;=> Hello world!

第一引数に t を指定すると標準出力に出力して nil を返します。第一引数に nil を指定すると標準出力にはなにも出力せず、生成した文字列を返します。

(format t "ABC~%")    ; ABC を出力して NIL を返却
(format nil "ABC~%")  ; なにも出力せず ABC を返却

~ を用いて引数の型や出力形式を指定することができます。

(format t "~d" 123)	;=> 123 ... 10進数
(format t "~x" 123)	;=> 7B ... 16進数
(format t "~o" 123)	;=> 173 ... 8進数
(format t "~b" 123)	;=> 1111011 ... 2進数
(format t "~f" 12.3)	;=> 12.3 ... 固定小数点形式浮動小数指示
(format t "~e" 12.3)	;=> 1.23e+1 ... 指数形式浮動小数指示
(format t "~g" 12.3)	;=> 12.3 ... ~f または ~e
(format t "~a" "ABC")	;=> ABC ... 文字列
(format t "~s" "ABC")	;=> "ABC" ... ""付き文字列

~ の後の数値で表示桁数を指定することができます。

(format t "[~5d]" 123)		;=> [  123]
(format t "[~5a]" "ABC")	;=> [ABC  ]

下記の様にすると 0埋めで表示することができます。

(format t "[~5,'0d]" 123)	;=> [00123]

d の前に : をつけると3桁毎にカンマ(,)を表示します。

(format t "[~10:d]" 1234567)	;=> [ 1,234,567]

d の前に @ をつけると +/- を必ず表示します。

(format t "[~10@d]" 1234567)	;=> [  +1234567]

アトム(atom)

数値(number)

数値には下記などの型があります。

整数(integer)

整数は下記の様に記述します。

123	; 正数
-123	; 負数

most-positive-short-float ~ most-positive-fixnum の範囲の整数は fixnum と呼ばれ、効率的に計算することができます。この範囲を超える正数は bignum と呼ばれ、効率は落ちますが何百桁でも正確に計算することができます。most-xxxx の値は処理系によって異なります。

* most-negative-fixnum
-4611686018427387904
* most-positive-fixnum
4611686018427387903

下記の様に2進数、8進数、16進数、N進数の数値も扱うことができます。

#b11001100	; 2進数(#b.../#B...)
#o777		; 8進数(#o.../#O...)
#x12EF		; 16進数(#x.../#X...)
#16R12EF	; N進数(#NR.../#Nr...)
小数(float)

小数は下記の様に表します。それぞれの精度は処理系に依存します。e/E は Common Lisp の仕様には定義されていませんが、SBCL 等でサポートされています。SBCL の実装では、1.23 や e, s, f の数値を single-float、d, l の数値を double-float として扱っているようです。

1.23		; 数値(固定小数点数)
1.23e2		; 数値(浮動小数点数(e/E))
1.23s2		; 数値(浮動小数点数:小精度(s/S))
1.23f2		; 数値(浮動小数点数:単精度(f/F))
1.23d2		; 数値(浮動小数点数:倍精度(d/D))
1.23l2		; 数値(浮動小数点数:長精度(l/L))

整数を小数に変換するには float を、小数を整数に変換するには floor, ceiling, truncate, round を用います。これらは整数部と余りの二値を返却します。round は四捨五入に似ていますが、繰り返し計算の誤差が小さくなるように銀行丸めというルールで 0.5 の時は偶数になる方向に整数化します。

(float 3)         ;=> 3.0 ... 小数化
(floor 3.5)       ;=> 3 あまり 0.5 ... 切り捨て
(ceiling 3.5)     ;=> 4 あまり -0.5 ...  切り上げ
(truncate 3.5)    ;=> 3 あまり 0.5 ... 0に近づく方向の整数
(truncate -3.5)   ;=> -3 あまり -0.5 ... 0に近づく方向の整数
(round 2.5)       ;=> 2 あまり 0.5 ... 銀行丸め
(round 3.5)       ;=> 4 あまり -0.5 ... 銀行丸め
分数(ratio)

Lisp では分数という型があり、分母と分子からなるひとつの値として管理します。

4/5		     ; 数値(分数)

numerator は分子、denominator は分母を取り出します。

(numerator 4/5)      ;=> 4
(denominator 4/5)    ;=> 5

float は分数を小数に変換します。rational, rationalize は小数を分数に変換します。rational は丸め誤差があるため下記のような結果になります。

(float 4/5)          ;=> 0.8
(rational 0.8)       ;=> 13421773/16777216
(rationalize 0.8)    ;=> 4/5
複素数(complex)

#C(...) は複素数を表します。

#C(5.1 2.4)	; 複素数

realpart は実部、imagpart は虚部を取り出します。

(realpart #C(5.1 2.4))    ;=> 5.1
(imagpart #C(5.1 2.4))    ;=> 2.4
文字列(string)

文字列は "..." で囲みます。

"ABC"		; 文字列

concatenate は文字列を連結します。

(concatenate 'string "ABC" "DEF")    ;=> "ABCDEF"

aref は文字列の N番目(0始まり)の文字を取り出します。

(aref "ABC" 1)     ;=> #\B ... 0から数えて1番目の文字を取り出す
文字(character)

文字は #\... で表します。

#\A                 ; 文字 A
#\あ                ; Unicode文字(あ)
#\U3042             ; Unicode文字(U+3042:あ)
#\HIRAGANA_LETTER_A ; Unicode文字(HIRAGANA_LETTER_A:あ)
#\tab               ; タブ文字(U+0009)
#\linefeed          ; ラインフィード(U+000A)
#\newline           ; 改行(U+000A)
#\return            ; キャリッジリターン(U+000D)
#\space             ; 空白文字(U+0020)

char-code は文字を文字コードに、code-char は文字コードを文字に変換します。

(char-code #\A)    ;=> 65
(char-code #\あ)   ;=> 12354
(code-char 65)     ;=> #\A
(code-char 12354)  ;=> #\HIRAGANA_LETTER_A
シンボル(symbol)

変数名、関数名、マクロ名などはシンボルで表します。シンボルには ' をつけます。リストの第1要素は ' が無くても関数やマクロなどのシンボルとみなされます。

'FOO		; シンボル

symbol-name, symbol-value, symbol-function はシンボルに与えられた名前、変数の値、関数を返します。下記の例では FOO という名前のシンボルに 123 という値や関数を設定し、それを参照しています。

(defparameter foo 123)
(defun foo (x) x)
(symbol-name 'foo)      ;=> "FOO"
(symbol-value 'foo)     ;=> 123
(symbol-function 'foo)  ;=> #<FUNCTION FOO>
真偽値(t/nil)

特殊な定数として、真を意味すると t と、偽を意味する nil があります。

t		; 真
nil		; 偽

リスト(list)

リスト(list)

( ) の中にスペースや改行で区切られた要素を並べたものをリストと呼びます。下記は format, t "ABC" という3個の要素を持つリストです。

(format t "ABC")

Lisp ではリストが記述された時、デフォルトで、最初の要素を関数やマクロなどのオペレーターとして認識します。最初の要素をオペレーターとして認識させたくない場合は、quote を用います。

(quote ("ABC" "DEF"))
(quote (123 456))

(quote ...) の代わりに '... と記述することもできます。

'("ABC" "DEF")
'(123 456)

もしくは明示的にリストを表す list を用います。

(list "ABC" "DEF")	;=> ("ABC" "DEF")
(list 123 456)		;=> (123 456)
空リスト(()/nil)

nil は空要素 () を示します。nil はアトムとしてもリストとしても扱われる特別な値です。

()
nil
要素抽出(car, cdr, first, second, ..., rest)

リストの中で最初の要素を CAR(カー)(Contents of the Address part of the Register)、残りの要素を CDR(クダー) (Contents of the Decrement part of the Register) と呼びます。car は最初の要素、cdr は残りの要素を抽出します。

(car '(a b c d))     ;=> A
(cdr '(a b c d))     ;=> (B C D)

cadr, caddr, cadddr は 2番目、3番目、4番目の要素を抽出します。

(cadr '(a b c d))     ;=> B 
(caddr '(a b c d))    ;=> C 
(cadddr '(a b c d))   ;=> D 

first, second, third, ..., tenth, rest, nth も使用できます。

(first '(a b c d))    ;=> A
(second '(a b c d))   ;=> B
(third '(a b c d))    ;=> C
(rest '(a b c d))     ;=> (B C D) ... CARを除いたリスト
(nth 2 '(a b c d))    ;=> C ... 0から数えて2番目の要素
リストの長さ(length)

length はリストの長さ(要素数)を求めます。

(length '(1 2 3))       ;=> 3
リスト連結(append)

append はリストを連結します。

(append '(1 2 3) '(4 5 6))    ;=> (1 2 3 4 5 6)
リスト検索(find, number)

find はリストから要素にマッチするものを探し、見つかった場合はその要素の値を返します。member は見つかった要素以降のリストを返します。

(find 2 '(1 2 3))        ;=> 2
(member 2 '(1 2 3))      ;=> (2 3)

find や member は要素の比較に eql を使用します。eql では文字列の比較ができないため、下記の様に #' を用いて比較関数(string=)を指定する必要があります。

(find "B" '("A" "B" "C") :test #'string=)       ;=> "B"
(member "B" '("A" "B" "C") :test #'string=)     ;=> ("B" "C")

コンスセル(cons)

cons は construct の略で、car とひとつの cdr からなる二値のペアを生成します。ペアはコンスセル(cons cell)と呼ばれます。コンスセルは (car . cdr) で表されます。

(cons 123 456)      ;=> (123 . 456)
(cons "ABC" "DEF")  ;=> ("ABC" . "DEF")

実はリストも内部的には複数の cons から構成されています。例えば、下記の2つの形式は同じリストを示します。

(list 1 2 3)			;=> (1 2 3)
(cons 1 (cons 2 (cons 3 nil)))	;=> (1 2 3)

配列(array)

配列は #(...) で表します。#2A(...) は二次元配列、#3A(...) は三次元配列を示します。

#(1 2 3)
#(123 12.3 "ABC")
#2A((1 2) (3 4))
#3A(((1 2) (3 4)) ((5 6) (7 8)))

make-array は配列を生成します。

(make-array 2)           ;=> #(0 0)
(make-array '(2 2))      ;=> #2A((0 0) (0 0))
(make-array '(2 2 2))    ;=> #3A(((0 0) (0 0)) ((0 0) (0 0)))

下記の様に配列要素の型を指定することができます。

(make-array 2 :element-type 'string)
(make-array '(2 2) :element-type 'double-float)

array-dimensions は配列の要素数を調べます。

(defparameter ar3 (make-array '(2 3 4)))
(array-dimensions ar3)           ;=> (2 3 4)

aref は配列内の要素を参照します。

(defparameter ar1 #("A" "B"))
(aref ar1 0)       ;=> "A"
(aref ar1 1)       ;=> "B"

(defparameter ar2 #2A(("A" "B") ("C" "D")))
(aref ar2 0 0)     ;=> "A"
(aref ar2 1 1)     ;=> "D"

aref に対して値を設定することも可能です。

(defparameter ar1 #("A" "B"))
(setf (aref ar1 0) "C")
(setf (aref ar1 1) "D")
ar1                ;=> #("C" "D")

ベクトル(vector)

配列の中でも一次元の配列はベクトルとも呼ばれ、vector で生成することもできます。

(vector 1 2 3)          ;=> #(1 2 3)
(vector "A" "B" "C")    ;=> #("A" "B" "C")

文字列(string)は、文字(character)の1次元配列(ベクトル)で構成されています。

(defparameter s1 (make-array 3 :element-type 'character))
(setf (aref s1 0) #\A)
(setf (aref s1 1) #\B)
(setf (aref s1 2) #\C)
s1                       ;=> "ABC"

ハッシュテーブル(hash-table)

make-hash-table はハッシュテーブルを作成します。ハッシュテーブルはキーと値のペアを複数保持できます。他の言語で連想配列や辞書と呼ばれるものに相当します。

(defparameter h1 (make-hash-table))

gethash はハッシュテーブルの要素に設定したり参照したりします。

(setf (gethash 'width h1) 600)       ; 値を設定
(setf (gethash 'height h1) 300)      ; 値を設定
(gethash 'width h1)                  ;=> 600
(gethash 'height h1)                 ;=> 300

hash-table-count はハッシュテーブルの個数を調べます。

(hash-table-count h1)        ;=> 2

remhash は特定のキーと値のペアを、clrhash はハッシュテーブルの全ペアを削除します。

(remhash 'width h1)
(clrhash h1)

構造体(defstruct)

defstruct は構造体を定義します。下記の例では width と height という属性を持つ box という名前の構造体を定義しています。属性のことを Lisp ではスロットと呼びます。

(defstruct box width height)

構造体の型を持つ変数は make-構造体名 で作成します。

(defparameter b1 (make-box :width 600 :height 300))

構造体名-スロット名 で構造体の属性にアクセスすることができます。

(box-width b1)               ;=> 600
(setf (box-width b1) 800)    ;=> 800

クラス(class)

クラス定義(defclass)

defclass はクラスを定義します。下記の例では width と height という属性(スロット)を持つ box という名前のクラスを定義しています。

(defclass box () (width height))

make-instance はクラスのインスタンスを生成します。

(defparameter b1 (make-instance 'box))

slot-value はインスタンスのスロットにアクセスします。

(setf (slot-value b1 'width) 600)    ;=> 600
(slot-value b1 'width)               ;=> 600

:initform は初期値を指定します。

(defclass box () (
  (width :initform 0)
  (height :initform 0)))

:accessor は slot-value 以外のアクセサを作成します。

(defclass box () (
  (width :initform 0 :accessor box-width)
  (height :initform 0 :accessor box-height)))
(defparameter b1 (make-instance 'box))
(print (box-width b1))
(setf (box-width b1) 600)

:initarg は初期化パラメータ名を指定します。

(defclass box () (
  (width :initform 0 :accessor box-width :initarg :width)
  (height :initform 0 :accessor box-height :initarg :height)))
(defparameter b1 (make-instance 'box :width 600 :height 300))
メソッド定義(defmethod)

defmethod はクラスなどの型に対してメソッドを定義します。下記の例では box というクラスに対して面積を求める cals-area というメソッドを定義しています。メソッドを定義する対象はクラスでなくてもよく integer などの型に対しても定義できます。

(defclass box () (
  (width :initform 0 :accessor box-width :initarg :width)
  (height :initform 0 :accessor box-height :initarg :height)))
(defmethod calc-area ((x box)) (* (box-width x) (box-height x)))
(defparameter b1 (make-instance 'box :width 600 :height 300))
(print (calc-area b1))

変数

パラメータ定義(defparameter)

defparameter は変数を定義し、初期値を設定します。

(defparameter a1 123)
(defparameter a2 "ABC")
代入(setq, setf)

setq, setf は変数に値を代入します。

(setq a1 123)    ; 変数a1に数値123を代入
(setf a2 "ABC")  ; 変数a2に文字列"ABC"を代入

setf では arefnth による参照に対しても値を設定することができます。

(defparameter a1 '(1 2 3 4 5))
(setq (nth 2 a1) 99)    ;=> ERROR
(setf (nth 2 a1) 99)    ; 2番目の要素に99を設定 
スペシャル変数(defvar)

defvar は他言語のグローバル変数に相当するスペシャル変数を定義します。

(defvar x)
(setq x 123)
(print x)		;=> 123

下記の様に初期値を指定することもできます。

(defvar x 123)
(print x)		;=> 123

慣習的にスペシャル変数は変数名を *...* で囲みます。

(defvar *default-width* 600)
(defvar *default-height* 200)
レキシカル変数(let)

let は他言語のローカル変数に相当するレキシカル変数を定義します。

(let (x y)
  (setf x 123)
  (setf y "ABC")
  (print x)     ;=> 123
  (print y))    ;=> "ABC"

下記の様に初期値を指定することもできます。

(let ((x 123) (y "ABC"))
  (print x)     ;=> 123
  (print y))    ;=> "ABC"
多値代入(multiple-value-setq)

multiple-value-setq, multiple-value-bind, multiple-value-list を用いると複数の値を返却するする関数からの戻り値を受け取ることができます。例えば切り捨てを行う floor 関数は切り捨てた整数と残りの数値の2値を返却しますが、これを下記の様にして受け取ることができます。

(defvar x1)
(defvar x2)
(multiple-value-setq (x1 x2) (floor 3.5))
(format t "x1=~d~%" x1)          ;=> x1=3
(format t "x2=~f~%" x2)          ;=> x2=0.5

(multiple-value-bind (y1 y2) (floor 3.5)
  (format t "y1=~d~%" y1)        ;=> y1=3
  (format t "y2=~f~%" y2))       ;=> y2=0.5
  
(defvar z1)
(setf z1 (multiple-value-list (floor 3.5)))
(format t "z1=~a~%" z1)          ;=> z1=(3 0.5)

演算子

演算子の記法には前置記法(+ 1 2)、中置記法(1 + 2)、後置記法(1 2 +)がありますが、Lisp では前置記法を用います。ポーランド記法とも呼ばれます。+ という演算子は、1 と 2 を引数として加算演算を行う関数とみなすこともできます。

比較演算子(=, /=, <, >, ...)

数値の比較には下記の比較関数を使用します。

(= exp1 exp2)    ; exp1exp2 が等しければ真
(/= exp1 exp2)   ; exp1exp2 が等しく無ければ真
(< exp1 exp2)    ; exp1 より exp2 が大きければ真
(> exp1 exp2)    ; exp1 より exp2 が小さければ真
(<= exp1 exp2)   ; exp1 より exp2 が大きいまたは等しければ真
(>= exp1 exp2)   ; exp1 より exp2 が小さいまたは等しければ真

文字列の比較には下記を使用します。

(string= str1 str2)       ; str1str2が等しければ真
(string/= str1 str2)      ; str1str2が等しくなければ真
(string< str1 str2)       ; str1よりstr2が辞書的に大きければ真
(string> str1 str2)       ; str1str2が辞書的に小さければ真
(string<= str1 str2)      ; str1str2が辞書的に等しいか大きければ真
(string>= str1 str2)      ; str1str2が辞書的に等しいか小さければ真
(string-equal str1 str2)  ; str1str2が等しければ真(大文字・小文字を区別しない)

下記の比較関数もあります。eq < eql < equal < equalp の順番で厳密な比較を行います。文字列を比較したり、1 と 1.0 を同じと見たしたければ equal か equalp を、"abc" と "ABC" を同じものと見なしたければ equalp を使用します。

(eq exp1 exp2)      ; exp1exp2 が等しければ真(厳密)
(eql exp1 exp2)     ; exp1exp2 が等しければ真(やや厳密)
(equal exp1 exp2)   ; exp1exp2 が等しければ真(やや緩やか)
(equalp exp1 exp2)  ; exp1exp2 が等しければ真(緩やか)

eq や eql は処理系によって結果が異なるので注意が必要です。文字列や数値が異なるメモリに格納される場合、eq は両者を異なっていると判断します。

(eq 12.3 12.3)  ; 処理系によって T だったり NIL だったり
算術演算子(+, -, *, /, ...)

算術演算関数には下記などがあります。

(+ exp1 exp2)	; exp1exp2
(- exp1 exp2)	; exp1exp2
(* exp1 exp2)	; exp1 × exp2
(/ exp1 exp2)	; exp1/exp2(分数)

下記の様に3つ以上の引数を指定することもできます。

(+ 2 3 4)	; 2 + 3 + 4 = 9
(- 6 2 1)	; 6 - 2 - 1 = 3
(* 2 3 4)	; 2 * 3 * 4 = 24
(/ 2 3 4)	; (2 / 3) / 4 = 1/6

1つだけ加算・減算する場合は 1+ や 1- を使用します。

(+1 exp)	; (+ exp 1) と同じ
(-1 exp)	; (- exp 1) と同じ

インクリメント・デクリメントもあります。

(incf x)	; (setf x (1+ x)) と同じ
(incf x n)	; (setf x (+ x n)) と同じ
(decf x)	; (setf x (1- x)) と同じ
(decf x n)	; (setf x (- x n)) と同じ

型判断(xxxp, ...)

下記などの引数の型を判断する関数もあります。末尾の p は 述語(predicate) を意味します。

(zerop exp)     ; expがゼロであれば真
(numberp exp)   ; expが数値であれば真
(plusp exp)     ; expが正数であれば真
(minusp exp)    ; expが負数であれば真
(oddp exp)      ; expが奇数であれば真
(evenp exp)     ; expが偶数であれば真
(null exp)      ; expがnilであれば真
(atom exp)      ; expがアトムであれば真
(integerp exp)  ; expが整数であれば真
(floatp exp)    ; expが小数点数であれば真
(symbolp exp)   ; expがシンボルであれば真
(stringp exp)   ; expが文字列であれば真
(listp exp)     ; expがリストであれば真
(consp exp)     ; expがコンスセルであれば真
型判断(typep)

typep を用いて下記の様に判断することもできます。

(typep 123 'number)           ;=> T
(typep 123 'integer)          ;=> T
(typep 1.23 'float)           ;=> T
(typep 1.23d3 'double-float)  ;=> T
(typep "ABC" 'string)         ;=> T
(typep #\A 'character)        ;=> T
(typep 'FOO 'symbol)          ;=> T
(typep '(1 2 3) 'list)        ;=> T
(typep (cons 1 2) 'cons)      ;=> T
型名を調べる(type-of)

下記の様にして型名を調べることもできます。

(type-of 123)        ;=> (INTEGER 0 4611686018427387903)
(type-of "ABC")      ;=> (SIMPLE-ARRAY CHARACTER (3))
(type-of '(1 2 3))   ;=> CONS
型情報を調べる(describe)

describe によって型の詳細情報を得ることができます。

* (describe 'integer)
COMMON-LISP:INTEGER
  [symbol]

INTEGER names the built-in-class #<BUILT-IN-CLASS COMMON-LISP:INTEGER>:
  Class precedence-list: INTEGER, RATIONAL, REAL, NUMBER, T
  Direct superclasses: RATIONAL
  Direct subclasses: BIGNUM, FIXNUM
  Sealed.
  No direct slots.

INTEGER names a primitive type-specifier:
  Lambda-list: (&OPTIONAL (LOW (QUOTE *)) (HIGH (QUOTE *)))
*
新たな型を定義する(deftype)

deftype で新たな型を定義することができます。

(deftype my-type () 'integer)

最小値、最大値を指定することもできます。下記の例では最小値に 0、最大値は無限大を指定しています。

(deftype my-type () '(integer 0 *))

satisfies で型に制約をかけることもできます。下記の例では evenp と plusp で正の偶数であるという制約をつけています。

(deftype my-type () '(satisfies evenp plusp))

and, or で複数の条件を指定することもできます。

(deftype my-type () '(and integer (satisfies evenp)))
リスト・ベクトル・文字列型変換(coerce)

coerce はリスト・ベクトル・文字列を変換します。

(coerce '(1 2)     'vector)          ;=> #(1 2)
(coerce '(#\a #\b) 'vector)          ;=> #(#\a #\b)
(coerce #(#\a #\b) 'list)            ;=> (#\a #\b)
(coerce "ab"       'list)            ;=> (#\a #\b)
(coerce '(#\a #\b) 'string)          ;=> "ab"
(coerce #(#\a #\b) 'string)          ;=> "ab"

条件分岐

条件分岐(if)

if構文は、cond が真であれば exp1 を、偽であれば exp2 を返します。

(if cond exp1 exp2)

使用例は下記の様になります。

(if (> 5 3)
  (format t "BIG!")
  (format t "NOT BIG!"))
条件分岐(when)

whenは、cond が真であれば、exp1, exp2, ... を実行します。

(when cond exp1 exp2 ...)

使用例は下記の様になります。

(when (> 5 3)
  (format t "BIG!")
  (format t "BIG!"))
条件分岐(unless)

unlessは、cond が偽であれば、exp1, exp2, ... を実行します。

(unless cond exp1 exp2 ...)

使用例は下記の様になります。

(unless (< 5 3)
  (format t "NOT BIG!")
  (format t "NOT BIG!"))
条件分岐(cond)

condマクロは、cond1 が真であれば exp1 を、cond2 が真であれば exp2 を返します。

(cond (cond1 exp1) (cond2 exp2) ...)

使用例は下記の様になります。

(defparameter x 40)
(cond ((> x 40) (format t "BIG!"))
      ((= x 40) (format t "EQUAL!"))
      ((< x 40) (format t "SMALL!")))
条件分岐(case)

caseマクロは、比較対象のシンボルが固定される場合に CONDマクロよりも楽に記述することができます。下記の例ではシンボル X の値により 2 の時は Two を出力します。いずれにも該当しない場合は Other を出力します。

(defparameter x 2)
(case x
  (1 (format t "One~%"))
  (2 (format t "Two~%"))
  (3 (format t "Three~%"))
  (otherwise (format t "Other~%")))

下記の様に条件を複数記述することもできます。

(defparameter x 2)
(case x
  ((1 2) (format t "One or two~%"))
  ((3 4) (format t "Three or four~%"))
  ((5 6) (format t "Five or six~%"))
  (otherwise (format t "Other~%")))

繰り返し

N回ループ(dotimes)

dotimes は var に 0 ~ num - 1 の数値を代入しながら、処理 exp1, exp2, ... を繰り返します。

(dotimes (var num) exp1 exp2 ...)

使用例は下記の様になります。

(dotimes (x 5) (print x))   ;=> 0 1 2 3 4
リストループ(dolist)

dolist は var にリストの要素を先頭からひとつずつ代入しながら、リストの個数分処理を繰り返します。

(dolist (var list) exp1 exp2 ...)

使用例は下記の様になります。

(dolist (x '(2 4 6 8)) (print x))    ;=> 2 4 6 8
無限ループ(loop)

loop は無条件に処理を繰り返します。ループを抜けるには return を用います。

(loop exp1 exp2 ...)

下記では X の階乗を求める関数 fact を定義して 5の階乗を計算しています。

(defun fact (x)
  (let ((result 1) (n 1))
    (loop
      (if (> n x) (return result))
      (setf result (* result n))
      (incf n))))
(print (fact 5))
条件ループ(while)

Common List では while はサポートされていません。必要であれば、下記の様にマクロを作成することができます。

(defmacro while (test &body body)
  `(do () ((not ,test)) ,@body))

(defparameter n 0)
(while (< n 5)
  (print n)
  (incf n))

もしくは loop while ... do ... の構文を使用することができます。

(defparameter n 0)
(loop while (< n 5) do
  (print n)
  (incf n))
DOループ(do)

doループは C言語などの for文に似ています。初期値を指定し、終了条件判断とインクリメントを行いながら処理を行います。

(do ((var init step) ...)
    (cond result)
    exp1 exp2 ...)

使用例を下記に示します。x と y に初期値として 0 を代入し、x の値は1ずつ、y の値は 10ずつインクリメントしながら、format で値を出力します。x の値が 5 になったら、do ループを抜け、戻り値として x + y を返却します。

(do ((x 0 (1+ x)) (y 0 (+ y 10)))
    ((= x 5) (+ x y))
    (format t "x=~a y=~a~%" x y))
逐次実行(progn)

progn はループではありませんが、exp1, exp2, ... を逐次実行します。

(progn exp1 exp2 ...)

if文で複数の式を実行した場合などに使用されます。

(defparameter n 20)
(if (> n 10)
  (progn (princ "O") (princ "K") (princ "!") (terpri))
  (progn (princ "N") (princ "G") (princ "!") (terpri)))

exp1, exp2, ... を実行した後、prog1 は最初の式の値を、prog2 は2番目の式の値を返します。

(prog1 exp1 exp2 ...)
(prog2 exp1 exp2 ...)

関数

関数定義(defun)

defun は関数を定義するマクロです。下記では加算を行う add マクロを定義して利用しています。関数では、一番最後に評価された式の値が関数の戻り値になります。

(defun add (x y)
  (+ x y))

(print (add 3 5))
オプション引数(&optional)

&optional は、それ以降の引数が省略可能なオプション引数であることを示します。省略した場合は nil となります。

(defun foo (a b &optional c d)
  (format t "a=~a b=~a c=~a d=~a~%" a b c d))

(foo 2 3 4)     ;=> a=2 b=3 c=4 d=NIL

オプション引数にはデフォルト値を指定することもできます。

(defun foo (a b &optional (c -1) (d -1))
  (format t "a=~a b=~a c=~a d=~a~%" a b c d))

(foo 2 3 4)     ;=> a=2 b=3 c=4 d=-1
残り引数(&rest)

&rest は、残りの引数を配列として受け取ります。

(defun foo (a b &rest values)
  (format t "a=~a b=~a values=~a~%" a b values))

(foo 2 3 4 5 6)    ;=> a=2 b=3 values=(4 5 6)
キーワード引数(&key)

&key はキーワードを用いて指定できる引数を示します。

(defun foo (&key a b c d)
  (format t "a=~d b=~d c=~d d=~d~%" a b c d))

(foo :d 10 :c 20)   ;=> a=NIL b=NIL c=20 d=10

初期値を指定することもできます。

(defun foo (&key (a 0) (b 0) (c 0) (d 0))
  (format t "a=~d b=~d c=~d d=~d~%" a b c d))

(foo :d 10 :c 20)   ;=> a=0 b=0 c=20 d=10
補助パラメータ(&aux)

&aux は let でレキシカル変数(ローカル変数)を定義する代わりに、引数定義部でレキシカル変数を定義します。

(defun add (a b &aux (z 0))
  (incf z a)
  (incf z b))

(add 3 5)         ;=> 8
再帰関数

関数から自分自身の関数を再帰的に呼ぶものを再起関数と呼びます。下記は引数 x の階乗を求める再起関数の例です。x が 0 であれば 1 を、1 以外であれば (x - 1) の階乗に x をかけたものを返します。

(defun fact (x) (if (= x 0) 1 (* (fact (1- x)) x)))

(fact 5)              ;=> 120
多値返却(values)

values を用いると関数から複数の値を返却することができます。下記の例では引数を2倍にした値と二乗した値を返却しています。

(defun func (x) (values (+ x x) (* x x)))
(func 5)                      ;=> 10 25
ラムダ関数(lambda)

lambda は関数名を持たないラムダ関数を停止します。mapcar などで一時的に使用される関数などで使用されます。

; ラムダ関数ではなく名前付き関数を指定する例
(defun func (x) (* 2 x))
(mapcar #'func '(1 2 3))                  ;=> (2 4 6)

; 名前を持たないラムダ関数を指定する例
(mapcar (lambda (x) (* 2 x)) '(1 2 3))    ;=> (2 4 6)
関数トレース(trace)

trace は指定した関数のトレースログを出力します。

(defun fact (x) (if (= x 0) 1 (* (fact (1- x)) x)))
(trace fact)
(fact 3)

トレースされた関数では下記のようなトレースログが出力されます。

0: (FACT 3)
  1: (FACT 2)
    2: (FACT 1)
    2: FACT returned 1
  1: FACT returned 2
0: FACT returned 6
組み込み関数

下記などの組み込み関数が用意されています。

(max x y z)    ; 最大値
(min x y z)    ; 最小値
(abs x)        ; 絶対値
(float x)      ; 整数や分数を小数に変換する
(mod x y)      ; (floor a b) の余り
(rem x y)      ; (truncate a b) の余り
(sqrt x)       ; ルート x
(expt x y)     ; x の y 乗
(exp x)        ; e の x 乗
(log x)        ; log x
(sin x)        ; sin x
(cos x)        ; cos x
(tan x)        ; tan x
(asin x)       ; asin x
(acos x)       ; acos x
(atan x)       ; atan x
(sinh x)       ; sinh x
(cosh x)       ; cosh x
(tanh x)       ; tanh x
(asinh x)      ; asinh x
(acosh x)      ; acosh x
(atanh x)      ; atanh x
(gcd x y)      ; x と y の最大公約数
(lcm x y)      ; x と y の最小公倍数
組み込み定数

下記などの組み込み定数が用意されています。

most-positive-fixnum    ; fixnumの最大値
most-negative-fixnum    ; fixnumの最小値
pi                      ; 円周率(3.14...)
関数参照(function, #')

function は引数で指定した関数名の関数を、関数の引数として渡したい場合などで利用します。例えば、mapcar という関数は第1引数にマップ関数を指定し、第2引数のリストに対してそれぞれマップ関数を適用した結果のリストを返却します。(function func) は #'func と省略系で記述することもできます。

(defun square (x) (* x x))
(mapcar (function square) '(1 2 3 4))    ;=> (1 4 9 16)
(mapcar #'square '(1 2 3 4))             ;=> (1 4 9 16)

マクロ

マクロ定義(defmacro)

defmacro はマクロを定義します。下記では、マクロ定義によりまず (* 2 3) というリストが生成され、これが実行されます。

(defmacro dbl (x) (list '* 2 x))
(dbl 3)                 ;=> 6

下記も 3 + 3 = 6 となりそうですが、リストを生成した後で評価するため、まず (+ (incf a1) (incf a1)) という式が生成され、a1 が 2 回インクリメントされるため、結果は 6 ではなく 7 となります。

(defmacro dbl (x) (list '+ x x))
(defparameter a1 2)
(dbl (incf a1))        ;=> 7
マクロ展開(macroexpand)

macroexpand はマクロがどのように展開されるのかを調べます。

(macroexpand '(dbl (incf a1)))     ;=> (+ (INCF A1) (INCF A1))
バッククォート(`)

マクロ定義ではバッククォート(')を用いると便利です。' も ` と同様リストをリストのまま評価しないで返却しますが、カンマ(,)をつけることにより、局所的に評価することが可能です。

'(1 (+ 2 3) 4)     ;=> (1 (+ 2 3) 4) ... 評価しないでリストのまま返却
`(1 (+ 2 3) 4)     ;=> (1 (+ 2 3) 4) ... 評価しないでリストのまま返却
`(1 ,(+ 2 3) 4)    ;=> (1 5 4) ... 一部だけ評価して返却

バッククォートを用いてマクロを下記の様に記述することができます。

(defmacro add (x y) (list '+ x y))     ; ` を使用しない方式
(defmacro add (x y) `(+ ,x ,y))        ; ` を使用した方法 
ボディ(&body)

&body は &rest に似た機能で、引数の残りをリストとして受け取ることができます。また、バッククォートの中で @, を使用すると、リストの中身を展開して参照することができます。

(defmacro if-do (test &body do-list) `(if ,test (progn ,@do-list)))

(if-do (> 5 3) (print "AAA") (print "BBB"))

; 下記に展開される
; (IF (> 5 3)
;   (PROGN (PRINT "AAA") (PRINT "BBB"))) 

マッピング

マップ関数(mapcar)

mapcar は関数とリストを受け取り、リストの各要素に関数を適用した結果のリストを返します。#' は関数を参照します。

(mapcar #'sqrt '(1 2 3))             ;=> (1.0 1.4142135 1.7320508)
(mapcar #'+ '(1 2 3) '(10 20 30))    ;=> (11 22 33)
(mapcar #'max '(1 6) '(2 4) '(4 3))  ;=> (4 6)
関数適用(apply)

apply は後続するリストを引数にして関数を実行します。

(apply #'+ '(1 2 3))           ; (+ 1 2 3) => 6
(apply #'+ 1 2 3 '(4 5 6))     ; (+ 1 2 3 4 5 6) => 21
関数適用(funcall)

funcall は後続する値を引数にして関数を実行します。

(funcall #'+ 1 2 3)            ; (+ 1 2 3) => 6
マップ(map)

map は mapcar と似ていますが、文字列、配列など、リスト以外の引数を指定することができます。第二引数には戻り値の型を指定します。

(defun func (c) c)
(map 'list #'func "abc")       ;=> (#\a #\b #\c)
(map 'vector #'func "abc")     ;=> #(#\a #\b #\c)
(map 'string #'func "abc")     ;=> "abc"
(map 'list (lambda (c) (char-code c)) "abc")  ;=> (97 98 99)

ファイル入出力

ファイルオープン(with-open-file)

with-open-file はファイルをオープンして読み書きを行います。下記はテキストファイルを開いて読み出す例です。in はストリームと呼ばれる変数です。

(with-open-file (in "data.txt")
  (loop for line = (read-line in nil)
    while line
    do (write-line line)))

下記はテキストファイルに書き込む例です。:direction には :input か :output を指定します。デフォルトは :input です。:if-exists にはファイルが存在していた場合の動作を :supersede(破棄) か :overwrite(上書き) で指定します。

(with-open-file (out "data.txt" :direction :output :if-exists :supersede)
  (write-line "AAA" out)
  (format out "~d~%" 123))

:external-format で文字コードを指定することができます。デフォルトは :utf-8 のようです。

(with-open-file (out "data.txt" :direction :output :if-exists :supersede :external-format :euc-jp) ...)
標準入出力(*standard-*)

標準で用意されているストリームには下記があります。

*standard-input*     ; 標準入力
*standard-output*    ; 標準出力
*error-output*       ; 標準エラー出力
ファイルオープン(open, close)

open や close も利用できます。

(defparameter line "")
(defparameter in (open "data.txt"))
(loop for line = (read-line in nil)
  while line
  do (write-line line))
(close in)
出力関数(write)

write は文字列も数値も書き出せます。文字列は "..." で囲まれます。改行しません。

(write expr [:stream stream])

write-line は文字列しか書き出せません。文字列は "..." で囲まれません。行末に改行します。

(write-line string [stream])

print は文字列も数値も書き出せます。文字列は "..." で囲まれます。行頭に改行します。

(print expr [stream])

princ は文字列も数値も書き出せます。文字列は "..." で囲まれません。改行しません。

(princ expr [stream])

prin1 は文字列も数値も書き出せます。文字列は "..." で囲まれます。改行しません。

(prin1 expr [stream])

format はフォーマットを指定して書き出します。

(format stream format args...)

terpri は改行を出力します。

(terpri [stream])

下記の様に使用するのが一番わかりやすいかも。

(princ expr)(terpri)
入力関数(read)

read はストリームから S式をひとつ読み出します。読み出すだけで評価はしません。

(read [stream [eof-flag [eof-value]]])

read-line はストリームから1行分を文字列として読み出します。

(read-line [stream [eof-flag [eof-value]]])

eof-flag は通常 T で、ファイルの終端に達した際に例外エラーとなります。eof-flag に nil を指定すると、ファイルの終端に達した場合に eof-value を返却します。eof-value のデフォルトは nil です。下記は、ファイルの終端に達した場合に nil を返却する例です。

(read-line in nil)

例外処理

handler-case を用いて、関数実行時にエラー(error)や警告(warning)が発生した場合の処理を記述することができます。

(defun func (x y)
  (handler-case (/ x y)
    (error (c)           ; エラー発生時の処理
      (print c)
      "ERROR"
    )
    (warning (c)         ; 警告発生時の処理
      (print c)
      "WARGING"
    )
    (:no-error (c)       ; 正常時の処理
      c
    )
  )
)

(func 5 0)               ;=> #<DIVISION-BY-ZERO {10015AB2C3}>  ERROR

メモ

Ubuntu のデフォルト値で Lisp のプログラムを vim で編集しているとカラフルすぎて目がチカチカするので、下記あたりを ~/.vimrc に設定しておくのが私は好きです。

:syntax off
hi MatchParen term=standout ctermbg=Black ctermfg=White

Copyright (C) 2022 杜甫々
初版:2022年2月13日 最終更新:2022年2月13日
http://www.tohoho-web.com/ex/lisp.html