Common Lispによるプログラミングに関する世界観です。Lispも参照のこと。
以下はLispでの足し算の例。
(+ 4 10 3)
これは4 + 10 + 3を意味する。
そして、数値である4や10以外に、カッコの中にカッコを作ることもできる。
(+ 4 (- 10 5) (/ 3 2))
引数の評価の順序は、引数(最初の要素以外)が左から右へと順番に評価され、その値がオペレータと呼ばれる最初の要素に渡される。
このような評価のルールのことを評価ルールという。
ただし、評価ルールから外れた特殊オペレータとして、関数でもマクロでもないquoteがある。quoteは、何もせずに引数をそのまま返す。
> (quote (+ 10 3)) (+ 10 3)
quoteは省略形である'を使って書くことができる。
'(+ 10 3)
quoteのことを引用符と呼ぶ。
なぜquoteを使うのか、それはLispでは単なるリストであっても関数として評価されてしまうため、そのような評価からリストを「保護」するためにある。
Lispのデータ型には、整数(100など)、ストリング(二重引用符で囲まれた文字列、"hoge"など)などの種類があるが、これ以外にシンボルとリストがある。
シンボルは単語で、元の文字列にかかわらず常に大文字となる。シンボルには引用符をつける。
> 'Hoge HOGE
リストは(~)のようなカッコでくくられた形式。リストに引用符をつけなかった場合、関数として評価されてしまうため、単にリストとしてデータを保持したい場合には引用符をつける。
> '(hoge 2 "Hoge") (HOGE 2 "Hoge")
引用符はひとつだけで内部すべての式が保護されるため、この場合hogeには'をつけなくてよい。
リストを作る関数にlistがある。引数として与えた式は評価されることに注意。
> (list 'hoge (+ 10 2) "Hoge") (HOGE 12 "Hoge")
リストとリストを結合したい場合はconsを使う。
> (cons '1 '(2 3)) (1 2 3)
リストから第一要素を取り出すにはcar、第一要素以外を取り出すにはcdrを使う。
Lispでは、プログラム自体がリストとして表現されるため、たとえばプログラム自身を吐き出すようなプログラムも簡単に記述できる。
ローカル変数はletを使って宣言する。letの次に変数名と値のリストが来て、その後にプログラム本体が来る。
> (let ((x 10) (y 20)) (+ x y)) 30
変数への代入はsetfを使う。そして、プログラム本体の最後の値が、そのプログラム式の返り値となる。
> (let ((x 10)) (setf x 20) x) 20
任意の文字列を画面に出力するにはformatを使う。文字列"~"の中の~Aは変数や式の値の挿入位置、~%は改行を表す。
> (format t "~A + ~A = ~A~%" 10 15 (+ 10 15)) 10 + 15 = 25 NIL
tは真を意味し、nilは偽を意味する。ifやwhenのような条件式では、nil以外はすべて真として扱われる。
> (if (= 1 1) 'true 'false) TRUE
関数はdefunで宣言する。
(defun add100 (x) (+ x 100)) > (add100 200) 300
関数名に対応する関数オブジェクトを取得するには、特殊オペレータのfunctionを使う。quoteの省略形が'であるように、functionの省略形は#'となる。
無名関数はlambdaで宣言できる。
(lambda (x) (+ x 500))
lambdaは以下のようにその場で実行することもできる。
> ((lambda (x) (+ x 500)) 100) 600
lambdaはクロージャと呼ばれることもある。クロージャとは、関数の宣言の外側にあるスコープの変数を、関数の中で使うことを指す。
配列は、#(1 2 3 4)のように表現する。二次元配列の場合は#2a((1 2 3) (10 20 30))とする。一次元の配列はベクターと呼ばれ、vector関数でも作成できる。
キーワードは:hogeのように、識別子に:をつける。キーワードはどこからでも参照できるため使い勝手がよく、さまざまなデータ構造で使われる。
リストを作成するlistの逆として、リストをコードとして実行するにはevalを使う。
> (eval '(+ 100 50)) 150
マクロは式の変換を行う機能で、defmacroを使って記述する。defunは値を返す関数を定義するが、defmacroは式の変換(マクロ展開)を行う。
(defmacro hoge! (x) (list 'hoge x))
(ANSI Common Lisp (スタンダードテキスト)を参考に執筆しました。)
2023.09.09編集
2023.11.01編集
注意:全部ではありません。ANSI Common Lisp (スタンダードテキスト)の中に出てくる関数やオペレータから、僕が気になるものをピックアップしました。
| 関数 | 説明 |
|---|---|
| let | ローカル変数を宣言する |
| setf | 変数に値を代入する |
| defun | グローバル関数を宣言する |
| labels | ローカル関数(その式の中でのみ使える関数)を宣言する |
| lambda | 無名関数を宣言する |
| progn | 順次実行し、最後の値を返すようなブロックを作る |
| block | 名前と返り値のついたブロックを作る |
| return-from | 値を返してその時点で実行を中断する |
| tagbody | gotoを使用できるブロックを作る |
| go | goto(ジャンプ)を行う |
| values | 多値を返す |
| quote | リストを評価せずそのまま返す(関数として実行しない) |
| list | リストを作る 二つの要素からなるリストは、二分木であると考えられる(右と左の要素が子ノードの親となる) |
| cons | リストを連結する |
| car | リストの第一要素を取り出す |
| cdr | リストの第一要素以外の要素を取り出す |
| mapcar | リストの全要素に関数を実行する |
| push | スタック(リスト)にプッシュ(追加)する |
| pop | スタック(リスト)からポップ(取り出し)する |
| make-array | 配列を作る #2a((1 2 3) (4 5 6))のように、#naを使うことで配列を直接記述できる(nは配列の次数) |
| aref | 配列の要素を取り出す |
| vector | 一次元の配列(ベクタ)を作る |
| make-hash-table | ハッシュテーブルを作る |
| gethash | ハッシュテーブルの中のキーに応じた値を取り出す |
| defstruct | 構造体(ストラクチャ)を作る |
| typep | 型を判定する |
| = | 値を等値であるか比較する |
| < | より小さい |
| > | より大きい |
| <= | より小さいかまたは等しい |
| >= | より大きいかまたは等しい |
| /= | 異なる |
| eql | 値に含めて型も等しいか比較する |
| if | すべての条件式の基礎 |
| when | 条件式が真である場合に本体を順々に評価する |
| unless | 条件式が偽である場合に本体を順々に評価する |
| cond | 複数の条件に対して、評価する内容を記述する |
| do | 反復処理を行う |
| dotimes | 指定した回数だけ反復処理を行う |
| catch | 例外をキャッチする |
| throw | 例外を投げる |
| unwind-protect | 例外に割り込まれたとしても評価される式を記述する |
| open | ファイルをオープンする |
| close | ファイルをクローズする |
| read-line | ファイルあるいは標準入力を一行読み込む |
| read | 一行のLisp式を読み込む |
| prin1 | プログラム用に出力する |
| princ | ユーザ用に出力する |
| format | フォーマット指定子を指定して出力する(変数の値を埋め込むことが可能) |
| + | 足し算 |
| - | 引き算 |
| * | 掛け算 |
| / | 割り算 |
| float | 浮動小数点数に変換する |
| expt | xnを求める |
| log | logn xを求める |
| exp | exを求める |
| sqrt | 平方根を求める |
| pi | 円周率を示す定数 |
| sin | サイン |
| cos | コサイン |
| tan | タンジェント |
| eval | リストをLispコードとして実行する |
| defmacro | マクロ(式を別の式に置き換える)を定義する |
| defclass | CLOS(Common Lispオブジェクトシステム)のクラスを定義する |
| defmethod | CLOSのメソッドを定義する |
| defpackage | パッケージ(シンボルの名前空間)を宣言する |
(ANSI Common Lisp (スタンダードテキスト)を参考に執筆しました。)
2023.09.10編集
Lispでは、括弧(~)の中に空白区切りで要素を羅列した、S式のリストでプログラムを記述する。
たとえば、Common Lispで10 + 5を計算するには、
> (+ 10 5) 15
のように、+のほうを先に書く(ポーランド記法)。
リストの中身を関数として評価したくない場合(リストそのものとして保護したい場合)は「'」をつける。
> '(1 2 3 4) (1 2 3 4)
consを使うことで、リストを連結できる。consをすることを「コンスする」と言う。
> (cons '1 '(2 3 4)) (1 2 3 4)
carを使うことで、リストの最初の要素を取り出せる。また、cdrを使うことで、リストの最初以外の要素を取り出せる。
また、マッピング関数mapcarを使えば、リストのすべての要素に関数を実行できる。
> (mapcar #'(lambda (x) (* x 3))
'(1 2 3 4 5))
(3 6 9 12 15)
このほか、Common Lispでは、ベクタ(一次元配列)や多次元配列、またはハッシュテーブルや構造体(ストラクチャ)などが利用できる。
配列はmake-arrayや、一次元配列の場合vectorで作成する。ハッシュテーブルはmake-hash-tableで作成する。
あるいは、多次元配列を作るために#na()という記法が利用できる(nは次元の数)。
#2a((1 2 3 4) (5 6 7 8))
配列の要素を取り出すにはaref、ハッシュテーブルでキーに対応する値を取り出すにはgethashを使う。
また、Common LispではCLOSを用いれば、オブジェクト指向のパッケージ(defpackage)・クラス(defclass)・メソッド(defmethod)を定義することもできる。
以下は参考文献。
2023.09.05
関数を定義する際に、引数を省略できるようにするには&optionalを使う。&optional以降の引数はオプションであり、デフォルトではnilが設定される。また&keyを使うことで、オプション引数の変数名と値をシンボルによって指定できる(キーワード引数)。
また、&restを使うと、最後の引数を表す変数にそれ以降のすべての引数がリストとして設定される。可変長引数を取る関数が実現できる。
(ANSI Common Lisp (スタンダードテキスト)を参考に執筆しました。)
2023.09.08
マクロの入力として、バッククォート(逆引用符)を使うと、リストをテンプレートのように作れる。また、「,」や「,@」を使うことで、テンプレートの中の一部分だけを式として評価できる。
たとえば:
> (setf x 10 y 20) 20 > `(x and y are ,x and ,y) (X AND Y ARE 10 AND 20)
あるいは:
> (setf hoge '(1 2 3 4)) (1 2 3 4) > `(hoge are ,@hoge) (HOGE ARE 1 2 3 4)
実際にdefmacroでマクロを書く時にこの記法は多用するので、覚えておいて損はない。
(ANSI Common Lisp (スタンダードテキスト)を参考に執筆しました。)
2023.09.08
Common Lispでは、defvarでスペシャル変数(グローバル変数)を宣言できる。スペシャル変数は*hoge*のように*~*と宣言する慣習がある。
> (defvar *hoge* 1000) *HOGE*
また、defparameterではスペシャル変数の値を変更できる。(defvarでスペシャル変数を書き換えることはできない。)
また、defconstantでは、変数でなく定数としてスペシャル定数を宣言することもできる。スペシャル定数を変更しようとするとエラーが出る。
Common Lispの旧来のダイナミックスコープ(すなわちdefvar)では、まずローカル変数が検索され、見つからない場合にグローバル変数が検索される。同じ名前のグローバル変数とローカル変数が定義されている状態では、たとえばグローバル変数にローカル変数の値が設定されたとして、設定されている状態ではグローバルにローカル変数がどこからでもアクセスでき、その状態がなくなると値が元に戻る。無用な混乱を避けるため、グローバル変数は*~*という名前にして区別すべきとされる。
現在のCommon LispではSchemeなどの成果を取り入れて静的スコープ(レキシカルスコープ)が標準となっているが、defvarを使えば動的スコープ(ダイナミックスコープ)を使うこともできる。
以下は参考文献。
2023.10.31
Common Lispの高階関数(関数を引数として取る関数)には、mapcar, apply, funcallなどがある。また、無名関数(名前のない関数で、通常の名前を付けて定義した関数と同じように利用できる)はlambdaで定義できる(ラムダ式と呼ばれる)。高階関数に渡す関数は通常の関数であってもラムダ式であってもよい。
| 関数 | 説明 |
|---|---|
| mapcar | リストの各要素に関数を実行し、その結果を格納したリストを返す |
| apply | 関数の引数としてリストを渡す。関数の引数が&restのようにリストに含まれている時に使う |
| funcall | 関数を実行する。関数を関数の引数として渡した上でそれを関数の中から実行するために使う |
| lambda | 無名関数(ラムダ式)を定義する |
以下は参考文献。
2024.08.13
evalは与えられた文字列をLisp式として評価する関数。簡単な式埋め込み型のテンプレート文字列として使える。evalによってLispはコンパイラ言語からインタープリタ言語に変貌する。
リフレクションとevalも参照のこと。
2024.08.13
以下の書籍が参考になります。この本はLispの入門者向けの書籍として定評があり、同じ著者の上級者向けの姉妹本であるOn LispとともにLisp入門書の標準とされています。
僕は、さらに高度なLispハッカーになるための方法として、「On Lisp」をおすすめします。
「On Lisp」は、マクロなどの手法を使って、Lispを拡張し、Lispコードを書くことでプログラミングに必要なさまざまな技術的な方法論と具体的なコードが分かる、素晴らしい本です。
はっきり言って、僕の書いたこのホームページぐらいの知識がある人は、この「On Lisp」という本以外には何も必要ありません。
「On Lisp」について言えることは、「まるで中学生時代の僕のためにあった本」とか、「中学生時代にこの本を読めばきっと人生は違っていた」ということです。
なぜか、「On Lisp」を読んでいると、僕は中学生時代の自分のことを思い出します。中学生時代には、僕はこの「On Lisp」と同じような「高い知性」を持っていたと僕は懐かしく思うからです。
なので、僕のこのホームページを読んだ人は、さらに高度なLispハッカーになるために、「On Lisp」を読むことをおすすめします。
2023.11.02