M.Hiroi's Home Page

Common Lisp Programming

お気楽 Common Lisp プログラミング入門

[ PrevPage | Common Lisp | NextPage ]

Lisp の基本

●最初は Hello, World

いちばん最初は、C言語で超有名な "Hello, World" を表示するプログラムを作りましょう。SBCL を立ち上げ、REPL で次のプログラムを入力してリターンキーを押してください。

* (format t "Hello, World")  <- リターンキーを押す
Hello, World
NIL

Hello, World と NIL が表示されました。左右のカッコ ( ) やダブルクォート " の数が合わないと、エラーになるので注意してください。また、カッコの中の format と t は英小文字で書きましたが、英大文字で書いても正しく動作します。空白は何個書いてもかまいませんが半角文字を使ってください。全角文字はいけません。

たとえば、最後に ) が余分についているとエラーになり、デバッガ (debugger) が起動します。

* (format t "Hello, World"))
Hello, World
NIL
*
debugger invoked on a SB-INT:SIMPLE-READER-ERROR in thread
#<THREAD "main thread" RUNNING {10005E85B3}>:
  unmatched close parenthesis

    Stream: #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000025193}>

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-IMPL::READ-RIGHT-PAREN #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000025193}> #<unused argument>)
0]

0] がデバッガのプロンプトです。restarts にリスタートのメニューが表示されるので、その番号または記号を入力してください。たとえば、abort と入力するとデバッガを抜けて REPL に戻ります。

(format t "Hello, World") はとても簡単なプログラムですが、これでも立派な Common Lisp プログラムなのです。

●Lisp ではリストが主役

それでは、Hello, World と表示したプログラムを詳しく説明しましょう。


上図のように、左右のカッコ ( ) で括られたものを「リスト (list)」といいます。カッコは半角でなければいけません。Lisp は LISt Processor の略称ですから、リストがない Lisp は何やらのないコーヒーどころの話ではありません。リストは Lisp にはなくてはならないデータ構造なのです。

リストには 2 種類の役割があります。1 つはデータを貯めることです。リストの中に格納されたデータを「要素」といいます。このプログラムのリストの中には、空白で区切られた 3 つの要素 FORMAT, T, "Hello, World" があります。format, t が英大文字になっていますが、その理由はあとで説明します。リストではないこれらの要素を「アトム (atom)」といいます。

リストは貨物列車にたとえるとわかりやすいでしょう。Lisp では、車両に相当するものを「コンスセル (cons cell)」といいます。貨物列車には多数の車両が接続されて運行されるように、リストは複数のコンスセルを接続して構成されます。1 つのコンスセルには、貨物(データ)を格納する「CAR (カー)」という場所と、連結器に相当する「CDR (クダー)」 という場所があります。


上図では、コンスセルを箱で表しています。左側の CAR がデータを格納する場所で、CDR が次のコンスセルと連結しています。この例では、3 つのコンスセルが接続されています。それから、最後尾のコンスセルの CDR には、リストの終わりを示すデータが格納されます。このデータについてはあとで説明します。

もうひとつは「関数 (function)」を呼び出す機能です。Lisp はリストが入力されると、第 1 要素を関数名、残りの要素をその関数に与える引数であると判断します。この例では FORMAT が関数で、T と "Hello, World" が引数となります。

関数は必ず実行結果を返します。最初の例では、NIL が関数 FORMAT の返り値です。Hello, World は FORMAT の働きで画面へ出力されたデータで、関数の返り値ではありません。つまり、FORMAT はデータを出力する関数なのです。

違う例を見てみましょう。単純な足し算を考えます。

* (+ 1 2 3)

6

+ は足し算を行う関数です。私達が普通に式を書く場合、1 + 2 + 3 のように演算子を真ん中に置きます。この書き方を「中置記法 (infix notation)」といいます。中があれば前と後もあるだろうと思われた方はいませんか。実はそのとおりで、「前置記法 (prefix notation)」と「後置記法 (postfix notation)」という書き方があります。

前置記法は演算子を前に置く書き方で、「ポーランド記法 (Polish Notation)」と呼ばれることもあります。たとえば、1 + 2 であれば + 1 2 と書きます。Lisp は前置記法を採用したプログラミング言語で、数式にカッコをつけてみると (+ 1 2) となり、Lisp のプログラムになります。

後置記法は演算子を後ろに置く書き方で、「逆ポーランド記法 (RPN : Reverse Polish Notation)」と呼ばれることもあります。1 + 2 であれば 1 2 + のように書きます。逆ポーランド記法の利点は、計算する順番に演算子が現れるため、カッコが不要になることです。逆ポーランド記法を採用したプログラミング言語には Forth があります。

関数 + の引数には数値が与えられます。数値データもアトムです。整数のほかにも、Common Lisp では浮動小数点数、多倍長整数、分数などを計算することができます。また、引数の数は 3 つと決まっているわけではなく、+ は複数の引数を受け取ることができます。

それでは、引数に数値以外のデータが与えられたらどうなるのでしょう。次の例を見てください。

* (+ (* 1 2) (- 3 4))

1

引数がリストになっています。このように、Lisp ではリストの要素にリストを持つことができるのです。これを図に表すと、次のようになります。


上図のように、リストは階層構造を作ることができますが、いちばん上の階層を「トップレベル (top level)」といいます。このように、リストを入れ子にできることが Lisp の大きな特徴のひとつなのです。

もうひとつ大事なことがあります。それは、Lisp は関数を実行する前に、引数の値をチェックすることです。このとき、引数がリストであれば、そのリストをプログラムとして実行します。関数 + の第 1 引数はリストなので、Lisp はそのリストの第 1 要素を関数として実行しようとします。* は掛け算をする関数です。この結果は 2 になります。

次に第 2 引数を調べます。これもリストなので - を関数として実行します。これは引き算をする関数です。この結果は -1 になります。これで + に与える引数をすべてチェックしたので、最後に + を実行します。そして、その結果が 1 になるわけです。

Lisp は与えられたデータに対し、定められた規則を実行していくことで動作します。このことを「評価する (eval, イーバル)」といいます。今まで述べたように、リストの場合は、その第 1 要素を関数として扱い、関数を呼び出す前にその引数を評価する、という規則なのです。

数値アトムの場合も規則があります。それは、評価されると自分自身を返す、というものです。(+ 1 2 3) を評価する場合でも、+ を実行する前に引数が評価されますが、数値アトムなのでそのままの値が + に渡されるのです。

●シンボルは変数として機能する

最初のプログラムに戻りましょう。

(format t "Hello, World")

FORMAT が評価される前に、引数が必ず評価されます。第 1 引数の T は「シンボル (symbol)」というデータです。今まで説明しませんでしたが、FORMAT, +, *, - もシンボルです。シンボルには名前が付いているので、ほかのシンボルと区別することができます。

なお Common Lisp では、シンボルを読み込むとき英大文字に畳み込むのがデフォルトの動作になります。つまり、REPL で format と入力すると、英小文字を英大文字に変換した FORMAT というシンボルが生成されます。t も同様に T というシンボルになります。本稿ではシンボルを英大文字で表記することにします。ただし、プログラムを書くときや関数名を表記するときは英小文字を使うことにします。

シンボルはデータを格納する変数としての役割と、関数定義を格納する役割を持っています。もうひとつ「属性リスト (property list)」というデータを格納できますが、あとで説明することにします。

Lisp はシンボルを評価すると、そこに格納されているデータを返します。ただし、リストの第 1 要素である場合は関数定義を取り出して実行します。FORMAT はリストの第 1 要素ですから、関数定義が取り出されて実行されたわけです。それでは、FORMAT にはどのような値が入っているのでしょうか。Lisp にたずねてみましょう。次のようにシンボル名を打ち込めば、そのシンボルに格納されているデータを求めることができます。

* format

=> エラー "The variable FORMAT is unbound."

エラーになりました。データを代入する操作をしない限り、シンボルにはデータが入っていないのです。関数型言語では、変数に値 (データ) を割り当てることを「束縛 (binding)」といいます。値が割り当てられた変数を「束縛変数 (bound variable)」といい、そうでない変数を「未束縛変数 (unbound variable)」といいます。

純粋な関数型言語 (Haskell など) の場合、束縛された変数は値を書き換えることができません。手続き型言語は代入により変数の値を書き換えることができますが、純粋な関数型言語に代入操作はありません。ちなみに、Lisp / Scheme は不純な関数型言語なので、変数の値を書き換えることができます。

Common Lisp の場合、変数に値を代入するには setq または setf という関数を使います。setq は Common Lisp 以前からある伝統的な関数です。M.Hiroi は古い人間なので setq を好んで使っています。

それでは、A というシンボルに数値を代入してみましょう。

* a

=> エラー "The variable A is unbound."
* (setq a 10)
;
; ワーニング (省略)
;

10
* a

10

(setq a 10) を実行するとワーニング (warnning) が表示されますが無視してください。A というシンボルに 10 が代入されました。ここで「おかしいな?」と思われたことでしょう。setq が評価される前に、引数であるシンボル A を評価するので、エラーになるはずです。

実は、setq は引数を評価しないで受け取る関数なのです。Common Lisp の場合、引数を評価しないで受け取る関数には、「特殊形式 (special form)」と「マクロ (macro)」があります。特殊形式やマクロは、プログラムの制御などを行う場合に使われます。setq は第 1 引数を評価せずにそのまま受け取り、第 2 引数を評価した結果をシンボルに代入します。たとえば、第 2 引数にリストを書けば、その実行結果がシンボルに代入されます。

* (setq a (+ 1 2 3))

6
* a

6
* (setq b a)

6
* b

6

2 番目の例のように、第 2 引数がシンボルであれば、そこに格納されている値が第 1 引数のシンボルに代入されます。

●代入は変数の値を破壊する

ここで、代入操作は変数が記憶している値を書き換えることに注意してください。


変数はひとつの値しか記憶できません。いま変数 var には 11 が記憶されています。setq で var に 12 を代入します。そうすると、var の値は 12 となり、元の値である 11 は失われてしまいます。元の値を書き換えてしまうことを「破壊的」といいます。逆に、値を変更しない操作は「非破壊的」といいます。値を読み取る操作は非破壊的です。したがって、元の値が必要な場合は、あらかじめ他の変数に値を移しておかなくてはいけません。


上図の例では、var の値を var1 に移しています。ただし、この場合でも var1 の値は破壊されることに注意してください。

●T と NIL は定数

それでは、T というシンボルはどうでしょう。setq を使って値をセットしていないのでエラーが出そうですが、プログラムは正常に動作しました。ある特定のシンボルには、あからじめ値がセットされています。T というシンボルもそのひとつです。では、T に何が入っているか確認してみましょう。

* t

T

自分自身が返ってきました。T というシンボルは条件判断で「真 (true)」を意味するシンボルです。反対に「偽 (false)」を意味するシンボルが NIL です。NIL の値には、NIL が最初から定義されています。実際に真偽を判定する場合、Lisp では NIL 以外のデータを真、NIL を偽と判定します。

NIL にはもうひとつ意味があります。次のように打ち込んでください。

* ()

NIL

( ) は中身の無いリスト、空リストのことです。NIL は空リストの意味もあるのです。T や NIL は Lisp にとって重要な役割を持つシンボルです。このようなシンボルは値を書き換えることができません。つまり、「定数 (constant)」として扱われます。

* (setq t 10)

=> エラー "T is a constant and thus can't be set."

●文字列

次に第 2 引数 "Hello, World" を見ましょう。" で括られたデータを「文字列 (string)」といいます。文字列もアトムで、評価されるとそれ自身を返します。

* "Hello, World"

"Hello, World"

これで format に渡される引数が評価されました。format は第 1 引数が T の場合、標準出力へデータを出力します。第 2 引数には書式を表した文字列を与え、それにしたがってデータを変換します。この場合は、文字列の内容を出力するだけです。

●評価しちゃだめ!

Lisp の変数は、どのようなデータでも格納することができます。ほかのプログラミング言語では、変数に格納するデータの種類をあらかじめ決めておかなければいけません。たとえば、C言語や Java では、変数 a に整数値を格納する場合は int a というように、a に格納するデータの種類を決めます。そして、a には整数値以外のデータを格納することはできません。

Lisp の場合、変数 A には整数値、文字列、シンボル、リストなど Lisp で扱うことができるデータであれば何でも格納することができます。ところで、整数値は setq で変数に代入できましたが、シンボルやリストを変数に代入できるのでしょうか。setq の第 2 引数は評価されることを思い出してください。

* (setq x 10)

10
* (setq y x)    <== 引数 X が評価され 10 が Y に代入される

10

変数 Y にシンボル X を代入する場合、setq にそのまま X を与えると、X が評価されてその値が Y に代入されてしまいます。リストの場合は、それがプログラムとして実行されるので、リスト自身を変数に代入することはできません。シンボルやリストを変数に代入するときは、引数が評価されては困るのです。そのため、引数を評価しないようにする関数が用意されています。

* 'x

X
* (setq y 'x)

X
* '(1 2 3 4)

(1 2 3 4)
* (setq y '(1 2 3 4))

(1 2 3 4)

引用符 ' を付けると、その次のシンボルやリストは評価されません。引用符は関数 quote の省略形で、'x は (QUOTE X) と同じ働きをします。quote は特殊形式で、引数を評価せずにそのまま返します。したがって、(setq y 'x) の場合、(QUOTE X) が評価されて X 自身が返り値となるので、シンボル X に格納されている値が取り出されるのではなく、X が関数 setq に渡されるのです。その結果、変数 Y にシンボル X を代入することができます。

同様に、リストの場合も引用符を付けることで、変数に代入することができます。この場合、リストは評価されない、つまり、プログラムとして実行されないので、最初の要素が関数である必要はありません。'(1 2 3 4) は (QUOTE (1 2 3 4)) に変換され、それが評価されて (1 2 3 4) というリスト自身が関数 setq に渡されるのです。この場合、リストはプログラムではなくデータとして扱われることになります。リストにプログラムとデータというふたつの役割を持たせていることが、ほかの言語とは最も異なる Lisp の特徴なのです。

普通の関数は引数を必ず評価するので、リストやシンボル自身をデータとして扱うために quote 関数を頻繁に使うことになります。(quote (1 2 3 4)) と書いていては面倒だし、プログラムが読みにくくなってしまいます。そこで、'(1 2 3 4) のような省略形が使われるようになりました。

●まとめ

ここで、ここまで出てきた Lisp のデータについて整理しましょう。アトムとリストを合わせて「S式 (symbolic expression)」または「フォーム (form)」といいます。データの関係をまとめると次のようになります。


今まで使ってきたデータの種類には、リスト、整数値、文字列、シンボルがあります。データの種類を「型 (type)」といいます。このほかにも、「配列 (array)」や「文字 (character)」など重要なデータ型がいくつかあります。

Lisp はS式を評価することで動作しますが、その評価規則はデータ型によって決められています。

  1. リスト
    リストの先頭の要素を関数とし、そこに定義されている処理を実行し、その結果を返す。ほかの要素は引数として関数に渡される。Lisp では、関数に引数を与えて呼び出すことを「適用 (apply)」という。
  2. シンボル
    そのシンボルに格納されている値を返す。
  3. その他
    自分自身を返す。

たとえば、(+ 1 2 3) を評価する場合、関数 + を評価する前に、引数の 1, 2, 3 を評価します。この場合、引数がリストやシンボルではないので、そのまま関数に渡されます。評価しても自分自身になるデータ型を「自己評価フォーム (self-evaluating form)」といいます。Common Lisp の場合、引数を評価する順番も決まっていて、左から右へ、つまりリストに並んだ順番で評価します。通常の関数では、引数は必ず評価されることを覚えておいて下さい。

ただし、特殊形式やマクロの場合は引数を評価しないことがあります。setq は引数を評価しませんでしたね。通常の関数は引数を評価するが、特殊形式やマクロは違うことに注意して下さい。

以上で Common Lisp の基本的な動作を説明しました。Common Lisp では、リストはプログラムとデータのふたつの役割を持っています。この特徴があるため、とても柔軟なプログラミングが可能になるのです。ですが、このままではまだ何もできませんね。Common Lisp には、あらかじめ用意されている便利な関数がたくさんあります。どのような関数があるか、具体的に見ていくことにしましょう。


Copyright (C) 2020 Makoto Hiroi
All rights reserved.

[ PrevPage | Common Lisp | NextPage ]