[Prev] [Index] [Next] [上端へ移動] [下端へ移動].

1.リスト処理

更新日時:2008年05月07日

慣れていない人の目には、Lispは不可思議なプログラミング言語である。Lispのコードには、いたるところに括弧がある。 「Lots of lsolated Silly Parentheses(奇妙な括弧が多い)」の略であると批判する人たちもいる。 しかし、この批判は不当である。LispはLISt Processingの略であり、括弧で囲んだリスト(list) (および、リストのリスト)を扱うプログラミング言語である。括弧はリストの境界を表す。 リストの直前にアポストロフィ、つまり、引用符`''を付ける場合もある。リストはLispの基本である。

1.1 Lispのリスト

Lispでは、リストを'(rose violet daisy buttercup)のように書く。 このリストの直前にはアポストロフィが1つ付いている。これはつぎのようにかくこともでき、 こちらの書き方にも慣れてほしい。

'(rose
  violet
  daisy
  buttercup)

このリストの各要素は異なる4つの花の名前である。個々の要素を空白で区切り、庭の花を石で囲うように括弧で囲む。

[上端へ移動] [下端へ移動]

Numbers, Lists inside of Lists

Lispでは、データとプログラムのどちらも同じ方法で実現する。 つまり、どちらも、単語や数やリストを空白で区切って括弧で囲んだリストである。 (プログラムはデータのようにも見えるので、プログラムは容易に他のプログラムのデータとなりえる。 これは、Lispの強力な機能である)

次はリストの例であり、リストの中にリストを含む。

'(this list has (a list inside of it))

このリストの要素は、`this'、`list'、`has'の単語と、リスト`(a list inside of it)'である。 内側のリストは、`a'、`list'、`inside'、`of'、 `it'の単語からできている。

[上端へ移動] [下端へ移動]

1.1.1 Lispのアトム

Lispでは、これまで単語と呼んできたものをアトム(atoms)と呼ぶ。この用語はアトム(原子)の歴史的な意味からきており、「不可分」ということである。 Lispに関していえば、リストに用いてきた単語はそれ以上には小さく分割できず、プログラムの一部として同じものを意味する。数や`+'のような1文字の記号についてもそうである。一方、アトムとは異なり、リストは部分に分割できる (節7.基本関数 carcdrcons

リストでは、アトムを空白で区切る。アトムは、括弧のすぐ隣にあってもよい。

技術的には、Lispのリストは、空白で区切ったアトムを囲む括弧、リストを囲む括弧、 アトムやリストを囲む括弧から成る。リストは、たった1個のアトムを含むだけでも、 まったく含まなくてもよい。何も含まないリストは()のように書き、空リスト(empty list)と呼ぶ。 空リストは、それ以外のものとは異なり、アトムであると同時にリストでもある。

アトムやリストを表示したものをシンボリック式(symbolic expressions)、 あるいは、より簡素にはS式(s-expressions)と呼ぶ。 用語「式(expression)」そのものでは、表示したアトムやリスト、 あるいは、コンピュータ内部に格納したアトムやリストを意味する。しばしば、 これらを区別せずに用語「式(expression)」を使う (さらに、多くの書籍では式の同義語として用語「フォーム(form)」を使う)。

われわれの宇宙を構成するアトム(原子)は、それらが不可分であると考えられた時代に命名されたものであるが、 物質原子は不可分ではないことが知られている。原子の一部を分割したり、ほぼ同じ大きさに分裂させたりできる。 物質原子は、その真の性質が発見される以前に命名されたのである。 Lispでは、 配列などのある種のアトムは構成部分に分割できるが、この分割機構はリストを分割する機構とは異なる。 リスト操作に関する限り、リストのアトムは分割できない。

英語の場合と同様に、Lispのアトムを構成する文字は、単語を作り上げる個々の文字とは異なる。 たとえば、南米のナマケモノを表す単語`ai'(ミツユビナマケモノ)は、 2つの単語`a'と`i'とはまったく異なる。

自然界には多種類の原子が存在するが、Lispには数種類のアトムしかない。 たとえば、37、511、1729などの数(numbers)、 `+'、`foo'、`forward-line'などの シンボル(symbols)である。これまで例にあげた単語はすべてシンボルである。 Lispの日常の習慣では、用語「アトム」をあまり使わない。というのは、 プログラマは扱っているアトムの種類を特定しようとするからである。 Lispのプログラミングでは、 リスト内のシンボル(やときには数)を扱う(括弧書き「(やときには数)」の 原文(and sometimes numbers)は、アトムを空白で区切って括弧で囲んであり、 しかも、Lispの句読点記号以外は含まないのでLispのリストである)。

さらに、二重引用符で囲まれたテキストは(文であろうと段落であろうと)アトムである。つぎに例を示す。

'(this list includes "text between quotation marks.")

Lispでは、句読点記号や空白を含みこのように囲まれたテキストは単一のアトムである。 この種のアトムは文字列(string)と呼ばれ、 コンピュータが人間向けに出力するメッセージに使う。文字列は、 数やシンボルとは異なる別の種類のアトムであり、使い方も異なる。

[上端へ移動] [下端へ移動]

1.1.2 リスト内の空白

リスト内の空白の個数はいくつでもよい。Lisp言語の視点からすれば、

'(this list
   looks like this)

はつぎとまったく同等である。

'(this list looks like this)

どちらの例もLispにとっては同じリストであり、`this'、`list'、`looks'、`like'、 `this'のシンボルからこの順に構成されたリストである。

余分な空白や改行は人間がリストを見やすくするためのものである。 Lispが式を読み取るとき、 余分な空白をすべて取り除く(ただし、アトムとアトムのあいだには、 これらを区切るために少なくとも1つの空白が必要である)。

奇妙に思えるかもしれないが、これまでの例で、 Lispのすべてのリストがどのようなものであるかを見てきた。 Lispのリストは多かれ少なかれこれまでの例のようなものであり、もっと長かったり複雑なだけである。 要約すれば、リストは括弧で囲まれたものであり、文字列は二重引用符で囲まれたものであり、 シンボルは単語のようなものであり、数は数字列である(鈎括弧やドットや特別な数種の文字を使う場合もあるが、 しばらくはこれらを使わない)。

[上端へ移動] [下端へ移動]

1.1.3 xyzzyのリスト入力補佐機能

xyzzyのLisp InteractionモードやlispmodeでLispの式を入力する時には、 Lispの式を読みやすく整形するためのコマンドを利用できる。たとえば、TABキーを押すと、 カーソルを置いた行を自動的に字下げする。あるリージョンのコードを正しく字下げするコマンドは、 indent-regionで行える。リストの各要素が、 どのリストに属するかがわかりやすくなるように字下げする。つまり、内側のリストの各要素は、 そのリストを囲むリストの要素よりも字下げされる。

また、parentagを導入すれば、 対応する括弧を強調表示して、対応関係がわかるようにする。 Lispに与える個々のリストは括弧の対応が取れている必要があるので、これはとても便利である(むしろ必須である)

[上端へ移動] [下端へ移動]

1.2 プログラムの実行

Lispのどんなリストも実行できるプログラムである。それを実行する(Lispの専門用語では評価(evaluate)する) と、コンピュータは、つぎの3つのうちの1つを行う。リストそのものを返して、 それ以外のことは何もしない。エラーメッセージを出す。 リストの先頭シンボルをなんらかのコマンドとして扱う(もちろん、普通は3番目の動作を望む!)。

前節の例においてリストの直前に付けた1つのアポストロフィ'クオート(quote)と呼ぶ。 リストの直前にこれを付けると、そのリストに関しては何もせずに字面どおりに扱うことをLispに指示する。 リストの直前にクオートがない場合には、リストの先頭要素を特別扱いし、コンピュータが従うべきコマンドとなる (Lispでは、これらのコマンドを関数(functions)と呼ぶ)。 リスト(+ 2 2)の直前にはクオートがないので、 Lispは、 +がリストの残りの部分に対して行うべき操作であると解釈し、後続の数を加算する。

www-modeで読んでいる場合は(そうでない場合はxyzzyにコピーして)、リストを評価するにはつぎのようにする。つぎの閉じ括弧の直後にカーソルを置いてからC-x C-eとタイプする

(+ 2 2)

ステータスバーに数4が表示されるはずである(専門用語では、たったいま「リストを評価」したのである。 ステータスバーとは画面の最下行(ミニバッファの下)のことで、テキストを表示する場所である)。 クオートしたリストについても同じことをやってみよう。 つぎのリストの直後にカーソルを置いてC-x C-eとタイプする。

'(this is a quoted list)

ステータスバーに(this is a quoted list)と表示されるはずである。

いずれの場合も、xyzzyの内部にあるLispインタープリタ(Lisp interpreter)と呼ばれるプログラムに指令、 つまり、式を評価せよという指令を与えたのである。 Lispインタープリタという名称は、表現の意味を追いかける人間の仕事、つまり、通訳(interpreter)からきている。

リストの一部ではないアトム、つまり、括弧に囲まれていないアトムを評価することもできる。 この場合もLispインタープリタは人間が読める表現をコンピュータの言語に変換する。 このことを説明するまえに、まちがいを起こした場合にLispインタープリタがどうするかを説明しよう。

[上端へ移動] [下端へ移動]

1.3 エラーメッセージ

偶然引き起こした場合は気にしないでよいが、ここではエラーメッセージを生成するようなコマンドをLispインタープリタに与えてみる。 これは無害であり、意図してエラーメッセージを生成することにある。専門用語を理解すれば、 エラーメッセージは有益でさえある。「エラー」メッセージと呼ぶよりは「ヘルプ」メッセージと呼ぶべきであろう。

リストの先頭要素に意味のあるコマンドもなく、クオートもしていないリストを評価してみよう。 上で用いたリストとほとんど同じであるが、その直前には引用符を付けない。 このリストの直後にカーソルを置いてC-x C-eとタイプする。

(this is an unquoted list)

ダイアログに関数が定義されていません:thisと表示される。Enterキーを押せばダイアログを消せます。

すでに知っていることをもとにすれば、このエラーメッセージを理解できるでしょう。

C-x C-eを入力すると、eval-last-sexpを対話的に呼び出します。 evalは「evaluate(評価する)」の、 sexpは「symbolic expression(シンボリック式)」の略称です。 結局、このコマンドは「最後のシンボリック式を評価する」ことを意味します。 つまり、カーソルの直前の式が評価されるのです。

Lispインタープリタはリストのアトムである`this'という単語を評価しようとします。 これにより、`関数が定義されていません:this' (this という関数は未定義)というエラーメッセージが表示されます。

このメッセージには`関数が定義されていません'と`this'という単語が含まれます。

関数、これは重要な用語である。ここでは、関数(function)とは、 コンピューターに何かを行わせるためのコンピューターに対する一連の命令であると定義しよう。

これで`関数が定義されていません:this' というエラーメッセージを理解することができます。 関数(つまり、`this'という単語) にはコンピュータに実行させるための命令が定義されていないということです。

一方で、(+ 2 2)を評価すると2に2を加算できるので、 シンボル+にはコンピュータが実行すべき命令列が与えられており、しかも、 それらは+に続く数を加算する命令であると推理できる。

[上端へ移動] [下端へ移動]

1.4 シンボル名と関数定義

これまでに説明してきたことをもとに、Lispの別の特徴を明確にすることができる。 +のようなシンボルは、それ自身はコンピュータが実行すべき命令列ではないという重要な特徴である。 そのかわりに、定義、すなわち、命令列を探すためにシンボルを(一時的に)使うのである。 われわれが見ているものは、命令列を探すための名前である。人の名前も同じように使われる。 筆者は`Bob'と呼ばれているが、私は`B'、`o'、`b'の3文字ではなく、意識のある生命体である。 名前そのものは私ではなく、私を指すために名前を使うのである。

Lispでは、1つの命令列に複数の名前を結び付けることができる。たとえば、 コンピュータの加算命令列をシンボルplusにも+にも結び付けられる(そのようなLispの方言もある)。 人間の世界でも、筆者を`Bob'と呼んだり`Robert'と呼んだりでき、それ以外の単語でもよい。

その一方で、シンボルには1つの関数定義しか結び付けられない。さもなければ、 どちらの定義を採用すべきかコンピュータが混乱する。これを人間の世界に当てはめると、 `Bob'という名前を持つ人は世界中で1人に限られることになる。 しかし、名前が参照する関数定義を変更するのは容易である。

[上端へ移動] [下端へ移動]

1.5 Lispインタープリタ

これまでの説明をもとに、リストの評価をLispインタープリタに命じると Lispインタープリタが何をするかを理解することができる。 まず、リストの直前にクオートがあるかどうかを調べる。クオートがあれば、 リストを返すだけである。一方、クオートがなければ、インタープリタはリストの先頭要素を調べ、 それに関数定義があるかどうかを調べる。関数定義があれば、 インタープリタはその関数定義内の命令列を実行する。さもなければ、 インタープリタはエラーメッセージを出力する。

これがLispの動作であり、単純である。すぐに説明するが、これに加えて複雑なことがらもあるが、 以上が基本である。当然、Lispプログラムを書くには、関数定義の書き方、名前への結び付け方、および、これらを読者やコンピュータに混乱のないように行う方法を知る必要がある。

[上端へ移動] [下端へ移動]

Complications

では、複雑なことがらの最初のことを説明しよう。 Lispインタープリタは、 リストに加えて、クオートもせず括弧で囲まれてもいないシンボルを評価できる。 Lispインタープリタは、変数(variable)としてのシンボルの値を決定しようとする。これについては変数に関する節で説明する。

複雑なことがらの2番目は、特殊な関数があり、これらは普通の方式のように動作しないことである。 これらをスペシャルフォーム(special forms)と呼ぶ。 関数の定義などの特殊なことを行うものであり、それらの個数は多くはない。 以下のいくつかの章では、重要なスペシャルフォームのいくつかを紹介する。

3番目で最後の複雑なことがらはつぎのとおりである。 Lispインタープリタが探しあてた関数がスペシャルフォームでなく、しかも、 それがリストの一部である場合には、 Lispインタープリタはリストの内側にリストがあるかどうかを調べる。 内側にリストがあれば、Lispインタープリタは内側のリストを処理してから、外側のリストを処理する。 内側のリストの内側にもリストが含まれている場合には、それを処理してから内側のリストを処理する。 つねに、もっとも内側のリストを最初に処理する。インタープリタはもっとも内側のリストの結果を得るためにそれを処理する。その結果はそれを含む式で使われる。

そうでない場合には、インタープリタは左から右への順で式を1つずつ処理する。

[上端へ移動] [下端へ移動]

1.5.1 バイトコンパイル

インタープリタには別の側面もある。 Lispインタープリタは2種類のものを解釈できる。 本書で取り上げている人が読める形式のコードと、特別な処理を施し人には読めない形式の バイトコンパイル(byte compiled)コードである。バイトコンパイルしたコードは、 人間向けのコードに比べて実行が速い。

byte-compile-fileのようなコンパイルコマンドを実行すれば、 人間向けのコードをバイトコンパイルコードに変換できる。バイトコンパイルコードは、 拡張子`.l'ではなく拡張子`.lc'で終わるファイルに収容するのが一般的である。 ディレクトリ`~/lisp'には両方の種類のファイルがあるが、人間が読むのは拡張子`.l'のファイルである。

[上端へ移動] [下端へ移動]

1.6 評価

Lispインタープリタが式を処理することを評価する(evaluation)と呼ぶ。インタープリタが「式を評価する」という。これまでにも、この用語を何度か使ってきた。

式を評価し終えると、Lispインタープリタは、 関数定義で与えられた命令列をコンピュータが実行した結果を返す(return)のが一般的であるが、 関数の処理を諦めてエラーメッセージを生成する場合もある (インタープリタ自体を別の関数に渡したり、「無限ループ」と呼ばれる無制限に同じことを繰り返すこともある。 これらの動作は一般的ではないので、ここでは無視することにする)。ほとんどの場合、インタープリタは値を返す。

インタープリタは、値を返すと同時に、カーソルを移動したりファイルをコピーしたりなどの別の動作も行う。 この種の別の動作は、副作用(side effect)と呼ばれる。 結果の表示などの人間が重要と考える動作は、 Lispインタープリタによっては「副作用」であることが多い。 この用語は奇妙に聞こえるかもしれないが、副作用の使い方を学ぶのはかなり簡単である。

まとめると、Lispインタープリタは、シンボリック式を評価するとほとんどの場合は値を返すが、副作用を伴うこともある。あるいは、エラーを生成する。

[上端へ移動] [下端へ移動]

1.6.1 内側のリストの評価

内側にリストを含んだリストを評価するときには、内側のリストを評価して得られた値を、 外側のリストを評価するときの情報として使う場合がある。そのために、内側の式を最初に評価するのである。 それが返した値を外側の式で使用する。

このような評価の過程を、加算の例題で調べることにしよう。つぎの式の直後にカーソルを置いてC-x C-eとタイプする。

(+ 2 (+ 3 3))

ステータスバーには数8が表示される。

Lispインタープリタで行われることは、まず内側の式(+ 3 3)を評価することであり、 これは値6を返す。続いて、外側の式を(+ 2 6)であるかのように評価し、これは値8を返す。 評価すべき外側の式はもうないので、インタープリタはこの値をステータスバーに表示する。

さて、キー列C-x C-eで起動されるコマンドの名前を理解するのは容易である。 コマンドの名前はeval-last-sexpである。sexpは 「シンボリック式(symbolic expression)」の略、evalは「評価(evaluate)」の略である。 コマンドの意味は、「直前のシンボリック式を評価する」である。

式の直後の行の先頭や式の内側にカーソルを置いても式を評価できるかどうか試してみよう。

つぎの式で試してみる。

(+ 2 (+ 3 3))

式の直後の空行の先頭にカーソルを置いてC-x C-eとタイプしても、 ステータスバーには値8が表示される。今度は、式の内側にカーソルを置いて試してみる。 最後の括弧のまえに(つまり、表示上は最後の括弧の上に)カーソルを置いて評価すると、 ステータスバーには値6が表示される!コマンドは式(+ 3 3)を評価したからである。

今度は、数の直後にカーソルを置いてみる。 C-x C-eとタイプすると数そのものを得る。 Lispでは、数を評価するとその数そのものを得るのである。これが数とシンボルの違いである。 +のようなシンボルで始まるリストを評価すると、+に結び付けた関数定義の命令列を実行した結果の値を得る。 つぎの節で説明するように、シンボルそのものを評価すると別のことが起こる。

[上端へ移動] [下端へ移動]

1.7 変数

xyzzy Lispでは、シンボルに関数定義を結び付けるように、シンボルに値を結び付けることもできる。 これらの2つは異なるものである。関数定義はコンピュータが遂行する命令列である。 一方で、値は、数や名前などの何かであり、変更できる (これが、そのようなシンボルが変数と呼ばれる理由である)。 シンボルの値としては、シンボル、数、リスト、文字列などの Lispの任意の式を取れる。 値を持つシンボルをしばしば変数(variable)と呼ぶ。

シンボルには関数定義と値の両方を同時に結び付けておくことができる。 あるいは一方か他方だけを結び付けておくことができる.これら2つは別々である。 これは、ケンブリッジという名称が、マサチューセッツの都市を表すと同時に、 「偉大なプログラミングセンター」のような名前の付加属性を持つことができるのに似ている。

あるいは、シンボルは箪笥であると考えてほしい。関数定義はある引き出しに入れてあり、 値は別の引き出しに入れてあるのである。関数定義を収めた引き出しの中身を変えることなく、 値を収めた引き出しの中身を変更でき、その逆もそうである。

[上端へ移動] [下端へ移動]

fill-column, an Example Variable

値を持つシンボルの例として変数fill-columnを取り上げよう。 xyzzyの各バッファでは、このシンボルに72とか70の値が設定されるが、それ以外の値の場合もある。 このシンボルの値を調べるには、それそのものを評価すればよい。 シンボルの直後にカーソルを置いてC-x C-eとタイプする。

fill-column

筆者の場合、C-x C-eとタイプするとxyzzyはステータスバーに数72を表示する。 この値は、筆者が本書を執筆中にfill-columnに設定してある値である。 読者のバッファでは異なるかもしれない。変数の値として返された値は、 関数の命令列を実行した結果返された値とまったく同じように表示されることに注意してほしい。 Lispインタープリタの視点からは、どちらも返された値である。 値が求まってしまえば、それがどのような式から得られたかは関係ないのである。

シンボルにはどのような値でも結び付けることができる。専門用語を使えば、変数には、72のような数、 "such as this"のような文字列、(spruce pine oak)のようなリストを束縛(bind)できる。 変数に関数定義を束縛することもできる。

シンボルに値を束縛する方法は何通りかある。

[上端へ移動] [下端へ移動]

1.7.1 Error Message for a Symbol Without a Function

fill-columnを変数としての値を知るために評価するときには, 単語の前後に括弧は付けない.これは,関数として使うわけではないからである.

fill-columnがリストの先頭要素だとすると、 Lispインタープリタはこれに結び付けられた関数定義を探そうとする。 しかし、fill-columnには関数定義はない。つぎを評価してみよう。

(fill-column)

ダイアログが表示され、関数が定義されていません:fill-columnと表示されるだろう。

[上端へ移動] [下端へ移動]

1.7.2 値のないシンボルに対するエラーメッセージ

値が束縛されていないシンボルを評価すると、エラーメッセージを得る。 たとえば、2足す2の例を用いて調べてみよう。つぎの式で、 最初の数2のまえの+の直後にカーソルを置いてC-x C-eをタイプすると、

(+ 2 2)

ダイアログに変数が定義されていません:+と表示される。

これはまえに見た関数が定義されていません:thisとは違うエラーメッセージである。 ここでは、シンボルには変数としての値がないのである。まえの場合は、 (`this'という)シンボルには関数定義がなかったのである。

+で試したことは、Lispインタープリタに+を評価させて、 関数定義ではなく変数の値を探させたのである。そうするために、 式を閉じる括弧の直後にカーソルを置くかわりに、シンボルの直後にカーソルを置いた。 そのため、Lispインタープリタは直前のS式、つまり、+そのものを評価したのである。

+には、関数定義はあるが、値は束縛されていないので、 変数としてのシンボルの値は空(void)である旨のエラーメッセージが報告されたのである。

[上端へ移動] [下端へ移動]

1.8 引数

どのように関数に情報が伝えられるかを見るために、お馴染みの2足す2の例を使ってみよう。 Lispでは、つぎのように書く。

(+ 2 2)

この式を評価すると、ステータスバーには数4が表示される。 Lispインタープリタが行ったことは、 +のあとに続く数の加算である。

+が加算した数のことを、関数+引数(arguments)と呼ぶ。 これらの数は、関数に与えられた、つまり、渡された情報である。

「argument(引数)」という用語は数学での用法からきており、 2人のあいだの議論のことではない。 ここでは、+という関数へ与えられた情報を意味する。 Lispでは、関数に対する引数は、 その関数のあとに続くアトムやリストである。これらのアトムやリストを評価して返された値が関数へ渡される。 関数が異なれば、必要な引数の個数も異なり、引数をまったく必要としない関数もある。

[上端へ移動] [下端へ移動]

1.8.1 引数のデータ型

関数に渡すデータの型は、関数が使用する情報の種類に応じて決まる。 +は数を加算するので、+のような関数への引数は数値である必要がある。 別の関数では別の種類のデータの引数が必要である。

たとえば、関数concatは複数の文字列を繋いで1つの文字列を生成する。 したがって、引数は文字列である。文字列abcdefを繋ぐ(concatinate)と、 1つの文字列abcdefが作られる。これはつぎの式を評価するとわかる。

(concat "abc" "def")

この式を評価して得られる値は"abcdef"である。

substringのような関数は、引数として文字列と数を取る。 この関数は文字列の一部分、つまり、第1引数の部分文字列を返す。この関数は3つの引数を取る。 第1引数は文字列であり、第2引数と第3引数は数で、部分文字列の開始位置と終了位置を示す。 これらの数は、文字列の先頭からの(空白や句読点を含む)文字の個数である。

たとえば、つぎを評価すると、

(substring "The quick brown fox jumped." 16 19)

ステータスバーには"fox"と表示される。引数は、1つの文字列と2つの数である。

substringに渡された文字列は、空白で区切られた複数の単語ではあるが1つのアトムである。 Lispは、2つの二重引用符のあいだにあるものを、たとえ空白があいだにあっても文字列として扱う。 関数substringは、他の方法では不可分なアトムから一部分を切り出すので、 「アトム粉砕機」の一種と考えてもよい。しかし、substringは、 文字列である引数から部分文字列を切り出すことができるだけであり、 数やシンボルなどのそれ以外の種類のアトムは扱えない。

[上端へ移動] [下端へ移動]

1.8.2 引数としての変数やリストの値

引数は、評価したときに値を返すシンボルでもよい。たとえば、 シンボルfill-columnそのものを評価すると数を返す。この数は加算に使える。

つぎの式のうしろにカーソルを置いてC-x C-eとタイプする。

(+ 2 fill-column)

fill-columnを単独で評価した値より2だけ大きな値が得られる。 筆者の場合には、fill-columnの値は72なので、結果は74である。

このように、評価すると値を返すシンボルを引数に使えるのである。 さらに、評価すると値を返すリストを引数に使うこともできる。たとえば、つぎの式では、 関数concatへの引数は、文字列"The "" red foxes."、さらに、 リスト(format nil "~d" (+ 2 fill-column))である。

(concat "The " (format nil "~d" (+ 2 fill-column)) " red foxes.")

この式を評価すると、(もし私のxyzzyのようにfill-columnが72になっていれば) ステータスバーには"The 74 red foxes."と表示される (最終結果の文字列に空白が含まれるように、単語`The'の直後と単語`red'の直前には空白が必要である。 (format nil "~d")はformatの書式の一つで、引数を10進数の文字列として返す。(わからなければ気にしなくて良い)

[上端へ移動] [下端へ移動]

1.8.3 可変個数の引数

concat+*などのある種の関数は、 任意個数の引数を取る(*は乗算のシンボルである)。 これは、以下の式のおのおのを通常の方法で評価するとわかる。 `=>'のあとのテキストがステータスバーに表示され、 `=>'は「の評価結果は」と読めばよい。

(+)       => 0

(*)       => 1

つぎは、関数に引数が1つの場合である。

(+ 3)     => 3

(* 3)     => 3

今度は、関数に引数が3つある場合である。

(+ 3 4 5) => 12

(* 3 4 5) => 60

[上端へ移動] [下端へ移動]

1.8.4 引数に誤った型のオブジェクトを指定

誤った型の引数を関数へ渡すと、Lispインタープリタはエラーメッセージを生成する。 たとえば、関数+は引数として数を仮定する。 数のかわりにクオートしたシンボルhelloを与えてみよう。 つぎの式の直後にカーソルを置いてC-x C-eとタイプする。

(+ 2 'hello)

そうすると、エラーメッセージを得る。何が起こったかというと、 +は数2に'helloが返す値を加算しようとしたのだが、 'helloが返した値はシンボルhelloであり、数ではない。 加算できるのは数のみである。したがって、+は加算を実行できなかったのである。

エラーメッセージは不正なデータ型です:hello :numberと表示される。

エラーメッセージのは簡単で、不正なデータ型、つまり、引数の型がまちがっているである。 +の引数は数字でなければならない。

[上端へ移動] [下端へ移動]

1.8.5 関数message

+のように、関数messageは任意個数の引数を取る。 この関数はユーザーにメッセージを表示するために使うので、ここで説明しておくのがよいであろう。

メッセージはステータスバーに表示される。たとえば、つぎのリストを評価すると、 ステータスバーにメッセージが表示される。但し、今までのようにC-x C-eで評価しても `t'としか表示されない。メッセージをステータスバーに表示させるには、*scratch*バッファにつぎのリストをコピー して、カーソルを最後の括弧の後ろに置き、C-jをタイプする。そうするとステータスバーに メッセージが表示される。(*scratch*バッファに`t'が追加されるが気にせず消して構わない)

(message "This message appears in the echo area!")

二重引用符のあいだの文字列は1つの引数であり、一塊で表示される

さて、文字列の中に~sがある場合、関数message~sをそのとおりに表示することはなく、 文字列のあとに続く引数を調べる。第2引数を評価し、その値を文字列の~sの位置に表示する。

つぎの式を先ほどのように*scratchバッファにコピーし、最後の括弧の後ろにカーソルを置いて C-jをタイプする。

(message "The name of this buffer is: ~s." (buffer-name(selected-buffer)))

ステータスバーにThe name of this buffer is: "*scratch*."と表示されるはずである。 (buffer-name(selected-buffer))はバッファ名を文字列として返し、 それを関数message~sの位置に挿入する。

値を整数として表示するには~sのかわりに~dを使う。 たとえば、fill-columnの値を含んだメッセージをステータスバーに表示するにはつぎの式を評価する。

(message "The value of fill-column is ~d." fill-column)

筆者のシステムでは、このリストを評価するとステータスバーに The value of fill-column is 72.と表示される

文字列の中に複数の~sがあれば、 文字列のあとに続く最初の引数の値が最初の~sの位置に表示され、 2番目の引数の値が2番目の~sの位置に表示されると続く。

例えば、つぎの式を評価すると

(message "There are ~d ~s in the office!"
         (- fill-column 14) "pink elephants")

ステータスバーに少々妙なメッセージが表示される。 筆者のシステムではThere are 58 "pink elephants" in the office!となる。

(- fill-column 14)が評価され、その結果の数が~dの位置に挿入される。 二重引用符の中の文字列"pink elephants"は1つの引数として扱われて~sの位置に挿入される (つまり、数と同様に、二重引用符で囲まれた文字列を評価するとそれ自身となる)。

最後は少々複雑な例で、数の計算を示すとともに、 ~sと置き換わるテキストを生成する式を式の内側で使う方法を示す。

(message "He saw ~d ~s"
         (- fill-column 34)
         (concat "red "
                 (substring
                  "The quick brown foxes jumped." 16 21)
                 " leaping."))

この例では、messageには、文字列"He saw %d %s"、 式(- fill-column 32)、関数concatで始まる式の3つの引数がある。 (- fill-column 32)を評価した結果は~dの位置に挿入され、 concatで始まる式の値は~sの位置に挿入される。

筆者の場合、この式を評価するとステータスバーにHe saw 38 "red foxes leaping."と表示される。

[上端へ移動] [下端へ移動]

1.9 変数への値の設定

変数に値を与える方法はいくつかある。1つの方法は関数setか関数setqを使うことである。 別の方法はletを使うことである(この過程を専門用語では変数を値に束縛(bind)するという)。

つぎの節ではsetsetqの動作を説明するとともに、 引数がどのように渡されるかも説明する。

[上端へ移動] [下端へ移動]

1.9.1 setの使い方

シンボルflowersの値としてリスト'(rose violet daisy buttercup)を設定するには、 つぎの式の直後にカーソルを移動してC-x C-eとタイプして式を評価する。

(set 'flowers '(rose violet daisy buttercup))

ステータスバーには、リスト(rose violet daisy buttercup)が表示される。 これは、関数set返したものである。副作用として、 シンボルflowersにリストが束縛される。つまり、シンボルflowersは変数とみなされ、 その値としてリストが与えられるのである(値を設定するこの過程は、Lispインタープリタにとっては副作用であるが、 人間にとっては興味のある主要な効果である。各Lisp関数はエラーがなければ値を返す必要があるが、関数の目的は副作用だけの場合もある)。

set式を評価したあとは、シンボルflowersを評価することができ、 設定した値が返される。つぎのシンボルの直後にカーソルを置いてC-x C-eとタイプする。

flowers

flowersを評価すると、ステータスバーにリスト(rose violet daisy buttercup)が表示される。

'flowers、つまり、直前にクオートを置いた変数を評価すると、 ステータスバーにはシンボルflowersそのものが表示される。つぎの例を試してみよう。

'flowers

setを使う場合、いずれの引数も評価してほしくない場合には、 両方の引数をクオートする必要があることに注意してほしい。上の例では、 変数flowersもリスト(rose violet daisy buttercup)も評価したくないので、 両者をクオートした(setの第1引数をクオートせずに使うと、 まず最初に第1引数が評価される。上の例でこれを行うと、flowersには値がないので、 `変数が定義されていません'というエラーメッセージを得る。 一方、flowersを評価して値が返される場合には、 setはその返された値に値を設定しようとする。このように関数を動作させたい場面もあるが、 そのような場面は少ない)。

[上端へ移動] [下端へ移動]

1.9.2 setqの使い方

実用上、setの第1引数をほとんどつねにクオートするはずである。 setで第1引数をクオートする組み合わせは多用されるので、 スペシャルフォームsetqが用意してある。 このスペシャルフォームはsetとほとんど同じであるが、第1引数を自動的にクオートするので、 引用符をタイプする必要はない。さらに、便利なように、setqでは、 複数の異なる変数に異なる値を 1つの式で設定することもできる。

setqを用いて、変数carnivoresの値を リスト'(lion tiger leopard)とするには、つぎの式を使う。

(setq carnivores '(lion tiger leopard))

これはsetを用いた場合とほぼ同じであるが、 setqでは第1引数を自動的にクオートするのが異なる (setqの`q'は、クオート(quote)を意味する)。

setを用いる場合は、つぎのようにする。

 (set 'carnivores '(lion tiger leopard))

さらに、setqは、複数の異なる変数に異なる値を代入するためにも使える。 第1引数には第2引数の値を束縛し、第3引数には第4引数の値を束縛し、……と続く。 たとえば、シンボルtreesに樹木名のリストを、 シンボルherbivoresにハーブの名前のリストを代入するにはつぎのようにする。

(setq trees '(pine fir oak maple)
      herbivores '(gazelle antelope zebra))

(式を1行に書いてもよいが、ページの幅に収まらない。人間には、 適切にフォーマットしたリストは読みやすいものである。)

「代入」という用語を用いたが、setsetqの動作を考える別の方法もある。 つまり、setsetqは、シンボルがリストを指す(point) ようにするのである。この考え方は非常によく使われ、うしろの章では、 名前の一部に「pointer」があるシンボルを見ることになる。この名称は、 シンボルには特にリストなどの値が結び付けらている、あるいは、 シンボルがリストを「指す」ように設定されていることに由来する。

[上端へ移動] [下端へ移動]

1.9.3 数え上げ

ここではカウンタでsetqを使う方法を示そう。 プログラムのある部分を何回繰り返したかを数え上げるために使える。 まず、変数に0を設定する。そして、プログラムを繰り返すたびに1を加える。 そのためには、カウンタの役割を果たす変数と2つの式、つまり、 カウンタ変数を0に初期化するsetqを用いた式と、 評価するたびにカウンタを増加するsetqを用いた式が必要である。

(setq counter 0)                ; 初期化式

(setq counter (+ counter 1))    ; 増加式

counter                         ; カウンタ

;以降のテキストは注釈である。)

これらの最初の式、つまり、初期化式(setq counter 0)を評価してから 3番目の式counterを評価すると、ステータスバーには数0が表示される。 続いて、2番目の式、増加式(setq counter (+ counter 1))を評価すると、 カウンタの値は1になる。そのため、ふたたびcounterを評価するとステータスバーには数1が表示される。 2番目の式を評価するたびに、カウンタの値は増加する。

増加式(setq counter (+ counter 1))を評価するとき、 Lispインタープリタは、もっとも内側のリスト、つまり、加算を最初に評価する。 このリストを評価するには、変数counterと数1を評価する必要がある。 変数counterを評価するとその現在値が得られる。 この値と数1+に渡され加算される。 この合計値がもっとも内側のリストの値として返され、さらに、 変数counterにこの新しい値を設定するsetqへ渡される。 したがって、変数counterの値が変更されるのである。

[上端へ移動] [下端へ移動]

1.10 まとめ

Lispを学ぶことは、登り始めがもっとも険しい小山を登るようなものである。 読者はもっとも困難な部分を登り終えたのであり、あとは、先へ進むほど楽になる。

本章をまとめるとつぎのようになる。

[上端へ移動] [下端へ移動]

1.11 演習問題

簡単な演習問題をあげておく。

・括弧の中にはない適当なシンボルを評価してエラーメッセージを生成してみよ。

・括弧の中に置いた適当なシンボルを評価してエラーメッセージを生成してみよ。

・1つずつではなく2つずつ増やすカウンタを作成せよ。

・評価するとステータスバーにメッセージを表示する式を書いてみよ。

[Prev] [Index] [Next] [上端へ移動] [下端へ移動]


このページはMeadow/Emacs memo(http://www.bookshelf.jp/)の Emacs Lisp プログラミングを改変したものです。
リンクはどのページでも自由にして下さい。
メールはkyupon@gmail.comまで送って下さい