次へ 目次へ 佐々木将人の個人ページへ
1 Lispの基本原理
Lispは、きわめて少数の原理で動くコンピューター言語です。
処理系を動かすと、何か一連の文字の塊を1個読み込み、「評価」と言って一定の規則に基づいて処理をし、その結果を返します。その繰り返しです。これだけでコンピューターが動作してしまうのです。
「何か一連の文字の塊」ですが、まず ( から ) までは1個になります。( と ) の間には何もなくてもかまいませんし、空白だけでもかまいませんし、なにか文字があってもかまいません。( と ) の間にさらに ( と ) があってもかまいませんが、一番外側の ( と ) とで1個扱いです。
( と ) とで一連扱いになるもの。これをリストと言います。Lispの由来 list processor のlistです。Lispはリストを処理するものなのです。
リストではないもの、これは正確にはいろいろあるのですが、基本のものとしてアトムをあげます。英語ではatomです。数字も含めて文字が並んでいたらアトムです。連続している必要がありますので、間に空白が入ったら空白の前までで1個です。改行は空白扱いで改行の前までで1個です。 ( と ) も空白・改行と同じ扱いで、 ( や ) の前までで1個です。(したがってアトムの終わりであることを示すために ( や ) の前に空白を置く必要はありません。)
評価の規則ですが、アトムについては、アトムの値として結び付けられたものを返します。したがって、Lisp処理系を立ち上げた直後など、アトムと何らかの値が結びついていない状態では、「値が結びつけられていません」という内容のエラーを返します。ただし、これには例外があって、アトムが数値である場合にはその数値に最初から結び付けられています。このように値を結び付けることを「束縛(する)」と言います。(英語ではbind。)言い換えると、アトムは原則として束縛されていないので、値を求めるためにはその前に束縛されていることが必要ですが、例外的に数値アトムについては、最初からその数値自身を束縛していることになります。
リストについては、リスト内の最初の要素を評価したものを関数として、2番目以降の要素をそれぞれ評価したものを引数として、関数に引数を渡した結果を返します。したがって、やはりLisp処理系を立ち上げた直後など、関数が定義されていない場合には、関数が定義されていないというエラーを返します。これにも同様に例外があって、Lisp処理系に最初から組み込まれている関数については、定義されている関数としてそのように実行されます。また、特殊形式というものがあって、(関数 引数……) の形式では処理しないものがあります。
Lispのプログラムは処理系を起動させた後で、リストで表現された関数を評価することに尽きるわけです。上で示したとおり、例えばリストの第1要素を取り出せということであれば (car '(a b c))でいいわけですし、これをcarではなくsaisyoとしたいのであれば、saisyo関数を定義した上で (saisyo '(a b c)) でいいわけです。引数なしの関数でもかまいません。またアトムに値が必要であれば(遅くともプログラムがその値を必要とするときまでに)束縛しておけばよいのです。どんな定義済の関数があるかは、調べておけばいいですし、なければ(もしくは使いにくければ)新しい関数を定義すればよいのです。LFEではLFE自身で定義している関数の他、Erlangの関数もたいていそのまま使えますので、Erlangを知っているとより有益です。処理系を起動させた後で、必要な関数を定義し、アトムに必要な値を束縛しておき、最後に最終目標となるリストを評価すれば、それがプログラムを実行したことになるわけです。
これがLispの基本のすべてです。
これであなたもLispのプログラムが作れます。(効率の良い、悪いはあるにせよ。)
……とはいえ……。
毎度一から関数定義やアトムへの値の束縛を入力していくのは大変ですので、普通は何らかの方策をとります。
さらに、Lispならではのテクニック、セオリー的なものも存在します。
でもそれは、「こうしたらもっと楽になるよ、わかりやすくなるよ」という話にすぎないのです。
これから先の話は、そういうレベルの話なのであって、けっして「これを理解しなければLispのプログラムは書けない」という話ではないのです。
おまじないの種明かし
先ほど、「(なお (a b c) の前の ' は、ある種のおまじないだと思ってください。)」と書きました。おまじないだとしてしまいましたが、これにはLispの仕組みが大きく影響しています。
「何か一連の文字の塊を1個読み込み、「評価」と言って一定の規則に基づいて処理をし、その結果を返します。」がLisp処理系の仕事なわけですが、その評価というのは、リストについては「最初の要素を評価したものを関数として、2番目以降の要素をそれぞれ評価したものを引数として、関数に引数を渡した結果を返します。」なのでした。 (car '(a b c))の例で言うと、最初の要素を関数 car として、そして2番目の要素 '(a b c) を引数として評価することになるのですが……。もし ' がついていない (a b c) を評価するのであれば、これもリストである以上最初の要素を関数 a として、2番目以降の要素 b 及び c をそれぞれ引数として、関数 a に引数 b c を渡した結果を返し、その結果を引数として関数 car を適用することになります。
ところが、どんな時にもこのルールが適用になるとすると、困ってしまう場合が出てくるのです(というか困る場合の方が実は多い)。
Lispのプログラムはリストで表現されるわけですが、Lispが取り扱うデータもまたリストで表現するのを基本としています。言い換えれば、プログラムとデータの表現上の本質的な差はないのです。例えば簡単な住所録のようなもの、氏名と出身都道府県のセットをLisp的に表現するには (佐々木将人 北海道) とリストで表現するのが自然でしょう。ところが、何らかのプログラムに何も考えずにこのデータを与えれば、リストなので第1要素を関数として評価する、「佐々木将人」という関数は……ってことになって困ってしまいます。
そこで登場するのが、quote関数です。(quote 引数) としておけば、この引数については「評価しないでそのままの値を返す」わけです。 (quote (佐々木将人 北海道)) だと、 (佐々木将人 北海道) というリストについては全く評価せずにそのまま返すわけです。名前を抜き出したいのであれば (car (quote (佐々木将人 北海道))) で、出身都道府県の方を抜き出したいのであれば (car (cdr (quote (佐々木将人 北海道)))) でいいことになります。(car関数とcdr関数を説明なしに使ってしまいましたが、今の時点では「これで期待通りの結果が出るのだ」という理解だけでかまいません。)
そして quote関数の略記形が ' なのです。いちいちquote関数のリストにしなくても、 ' を冒頭につけて '(佐々木将人 北海道) とすればいい。
おまじないの正体は、quote関数の略記形だったのです。そしてquote関数の略記形が ' の1文字であるということは、それだけ出番が多い、言い換えるなら評価されると困る場合というのが実は多いということでもあるのです。
※
伝統的なLispにおけるsetq関数は、 (setq a 125) の形で a に125を束縛することで、他のプログラミング言語における変数を実現しているわけですが、 (setq a 125) をリストと解釈すれば a も( 125 も)評価しなければならないはずですし、実際setq関数のもとになったset関数では、a は評価の対象です。でも、変数で評価の必要がない場合にいちいち ' をつけなければならないのも煩雑なので、setq関数が使われるようになりました。理論的には、setq関数では引数を評価しないので、リストとしての評価は行わないことになります。例外にあたるので、特殊形式と呼んでいます。
Lisp Flavoured Erlang
LFEではsetqではなくsetです。 (setq a 125) はLFEではエラーになりますし、 (set a 125) としないといけないのです。(ああ、ややこしい!)
「評価しないで」のquoteがあるのなら……
「評価しないで」のquote関数があるのであれば、その逆に、意識して「評価して」という関数もあります。意識して……とあえて書いたのは、通常の「何か一連の文字の塊を1個読み込み、「評価」と言って一定の規則に基づいて処理をし、その結果を返します。」の一連の処理の中に「評価」は組み込まれているのですが、そのことをふまえてあえてさらに「評価」をする必要がある場合があるってことなのです。
具体的には eval関数がそれになります。
eval関数は引数に対しあえて評価を行います。
ちなみに、「何か一連の文字の塊を1個読み込み、「評価」と言って一定の規則に基づいて処理をし、その結果を返します。」について、「何か一連の文字の塊を1個読み込」む関数は read関数、「その結果を返」す関数は print関数なので、「何か一連の文字の塊を1個読み込み、「評価」と言って一定の規則に基づいて処理をし、その結果を返します。」作業を「read-eval-print」、これを繰り返しているので「read-eval-print loop」と呼んでいます。「REP」もしくは「REPL」ともいい、Lisp以外の言語でも用いられます。
(2022.10.25. 初版)
(2023.6.9. 改訂)
次へ 目次へ 佐々木将人の個人ページへ