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

3.関数定義の書き方

更新日時:2008年05月10日

リストを評価するとき、Lispインタープリタは、リストの先頭のシンボルに関数定義が結び付けられているか どうかを調べる。いいかえれば、シンボルが関数定義を指すかどうかを調べる。そうならば、 コンピュータは定義内の命令列を実行する。関数定義を持つシンボルを、単に関数と呼ぶ (しかし、正確には、定義が関数であり、シンボルはそれを指すだけである)。

An Aside about Primitive Functions

3.1 スペシャルフォームdefun

Lispでは、mark-whole-bufferのようなシンボルには、関数として呼ばれたときにコンピュータが 実行するコードが結び付けられている。このコードを関数定義(function definition)と呼び、 シンボルdefun(define function(関数を定義する)の略)で始まる Lispの式を評価することで作成する。 defunは、その引数を通常のようには評価しないので、 スペシャルフォーム(special form)と呼ばれる。

以下の節では、mark-whole-bufferのようなxyzzyのソースコードの関数定義を調べる。 本節では、関数定義がどのようなものかを理解してもらうために、簡単な関数定義を説明する。 例を簡単にするために、算術演算を使った関数定義を取り上げる。算術演算を使った例が嫌いな人もいるであろうが、 落胆しないでほしい。残りの節で説明するコードには、算術演算や数学はほとんどない。そのほとんどは、 テキストを扱う。

関数定義は、単語defunに続く最大で5つの部分から成る。

  1. 関数定義を結びつけるシンボルの名前。
  2. 関数に渡される引数のリスト。関数に引数を渡さない場合には、空リスト()を指定する。
  3. 関数の説明文(省略できるが、付けることを強く推奨する)。
  4. M-xに続けて関数名を入力したり、適当なキー列をタイプして使えるように、 関数を対話的にするための式。省略できる。
  5. コンピュータに動作を命じるコード。関数定義の本体(body)

関数定義の5つの部分を、つぎのような雛型にまとめて考えるとわかりやすい。

(defun 関数名 (引数...)
  "省略可能な関数の説明文..."
  (interactive 引数に関する情報) ;    省略可能
  本体...)

例として、引数を7倍する関数のコードを示す(この例は、対話的関数ではない。これについては、See 節 3.3 関数を対話的にする)。

(defun multiply-by-seven (number)
  "Multiply NUMBER by seven."
  (* 7 number))

この定義は、括弧とシンボルdefunで始まり、関数名が続く。

関数名のあとには、関数に渡される引数のリストが続く。このリストを、 引数リスト(argument list)と呼ぶ。この例では、リストには1つの要素、 シンボルnumberのみがある。関数が使われると、関数への引数として使われた値が このシンボルに束縛される。

引数の名前としては、単語numberのかわりに別の名前を指定してもよい。たとえば、 単語multiplicandでもよい。引数の値の種類がわかるように単語numberを選んだ。 同様に、関数の実行において引数の役割を表すmultiplicand(被乗数)を選んでもよかった。 引数をfoogleとも書けるが、これでは何を意味するか不明なのでよくない。 名前の選択はプログラマの責任であり、関数の意味を明らかにするように選ぶべきである。

引数リストのシンボルにはどんな名前を選んでもよく、他の関数で使っているシンボルの名前でもよい。 引数リストに使用した名前は、その定義において私的である。つまり、その定義において名前で参照した実体は、 その関数定義の外部で同じ名前で参照する実体とは別である。たとえば、家族のあいだでは読者の愛称は 「ショーティ」だとしよう。家族の誰かが「ショーティ」といった場合には、読者のことである。しかし、 別の家族が「ショーティ」といった場合には、別の誰かのことである。引数リスト中の名前は関数定義に私的なので、 関数本体の内側でそのようなシンボルの値を変更しても、関数の外部の値には影響しない。 let式でも同様な効果が得られる(See 節 3.6 let)。

引数リストには、関数の説明文である文字列が続く。M-x:describe-functionに続けて 関数名をタイプしたときに表示されるのは、この文字列である。aproposなどのある種のコマンドでは複数行の 説明文のうち最初の1行のみを表示するので、関数の説明文を書く場合には、最初の1行を1つの文にすべきである。 また、describe-functionで表示した場合に、表示が変にならないように、 説明文の2行目以降を字下げしないこと。説明文は省略できるが、あると有益なので、 読者が書くほとんどの関数には指定するべきである。

上の例の3行目は関数定義の本体である(当然、ほとんどの関数の定義は、この例よりも長いはずである)。 ここでは、本体はリスト(* 7 number)であり、numberの値を7倍する(xyzzy Lispでは、 +が加算であるように、*は乗算である)。

関数multiply-by-sevenを使うときには、引数numberは読者が指定した実際の数に 評価される。multiply-by-sevenの使い方を示すが、まだ、評価しないでほしい。

(multiply-by-seven 3)

関数を実際に使用すると、次節の関数定義に指定したシンボルnumberには、値3が与えられる、つまり、 「束縛」される。関数定義ではnumberは括弧の内側にあるが、関数multiply-by-sevenに 渡される引数は括弧の内側にはないことに注意してほしい。関数定義において引数を括弧で囲むのは、 コンピュータが引数リストの終わりと関数定義の残りの部分を区別できるようにするためである。

さて、この例を評価すると、エラーメッセージを得る(実際に試してみるとよい)。これは、 関数定義を書いたけれども、その定義をコンピュータに与えていないからである。つまり、 関数定義をxyzzyにインストール(あるいは、ロード)していないからである。関数のインストールとは、 Lispインタープリタに関数の定義を教える操作である。次節では、インストールについて説明する。

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

3.2 関数定義のインストール

www-mode等で読んでいる場合には、multiply-by-sevenの関数定義を評価してから (multiply-by-seven 3)を評価すれば、関数multiply-by-sevenを試すことができる。 関数定義をもう一度つぎにあげておく。関数定義の最後の括弧の直後にカーソルを置いてC-x C-eと タイプする。すると、ステータスバーにmultiply-by-sevenと表示される (関数定義を評価すると、その値として定義された関数の名前が返される)。同時に、 この操作で関数定義がインストールされるのである。

(defun multiply-by-seven (number)
  "Multiply NUMBER by seven."
  (* 7 number))

このdefunを評価すると、xyzzyにmultiply-by-sevenをインストールしたことになる。 これで、forward-wordや編集関数などと同じく、この関数もxyzzyの一部である (xyzzyを終了するまで、multiply-by-sevenはインストールされたままである。 xyzzy起動時に自動的にコードをロードするには 3.5 コードの恒久的インストールを参照)。

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

The effect of installation

つぎの例を評価すれば、multiply-by-sevenをインストールした効果がわかる。 つぎの式の直後にカーソルを置いてC-x C-eとタイプする。ステータスバーに数21が表示されるはずである。

(multiply-by-seven 3)

必要ならば、M-x:describe-functionに続けて関数名multiply-by-sevenをタイプすれば、 関数の説明文を読むことができる。これを行うと、つぎのような内容のウィンドウ`*Help*'が画面に現れる。

multiply-by-seven: a Lisp function.

multiply-by-seven NUMBER

Multiply NUMBER by seven.

(画面を単一のウィンドウに戻すには、C-x 1とタイプする。)

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

3.2.1 関数定義の変更

multiply-by-sevenのコードを変更するには、書き変えればよい。 旧版のかわりに新版をインストールするには、関数定義を再度評価する。xyzzyではこのようにコードを修正すればよく、 非常に簡単である。

例として、7を掛けるかわりに、数そのものを7回足すように関数multiply-by-sevenを変更する。 同じ結果を得るが、その方法が異なる。同時に、コードに注釈を加えよう。注釈とは、Lispインタープリタは 無視するテキストであるが、人には有用であり意味を明らかにする。この例では、「第2版」が注釈である。

(defun multiply-by-seven (number)       ; 第2版
  "Multiply NUMBER by seven."
  (+ number number number number number number number))

セミコロン`;'に続けて注釈を書く。 Lispでは、行の中でセミコロンに続くものはすべて注釈である。 注釈は行末で終わる。 2行以上にわたる注釈は、各行をセミコロンで始める。

この版の関数multiply-by-sevenをインストールするには、 最初の版を評価したのと同じように評価すればよい。すなわち、 最後の括弧の直後にカーソルを置いてC-x C-eとタイプする。

まとめると、xyzzy Lispでコードを書くには、関数を書いてインストールしてテストし、必要に応じて、 修正や機能強化して再インストールする。

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

3.3 関数を対話的にする

関数を対話的にするには、関数の説明文のあとにスペシャルフォームinteractiveで始まるリストを置く。 こうすれば、M-xに続けて関数名をタイプするか、関数に束縛したキー列をタイプすれば 対話的関数を起動できる。たとえば、next-lineを起動するにはC-nとタイプし、 open-lineを起動するにはC-oとタイプする。

対話的関数を対話的に呼び出した場合には、関数が返した値は自動的にはステータスバーに表示されない。 これは、対話的関数を呼び出すのは、単語単位や行単位の移動などの副作用のためであり、 返された値は必要ないからである。キーをタイプするたびに返された値をステータスバーに表示すると、とても煩わしい。

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

対話的multiply-by-seven

multiply-by-sevenの対話的な版を作って、スペシャルフォームinteractiveの使い方と ステータスバーに値を表示する1つの方法を示そう。

コードはつぎのとおりである。

  (defun multiply-by-seven (number)       ; 対話的版
  "Multiply NUMBER by seven."
  (interactive "p")
  (message "The result is ~d" (* 7 number)))

このコードの直後にカーソルを置いてC-x C-eとタイプして、このコードをインストールする。 ステータスバーには関数名が表示されるはずである。そうすれば、C-u、数、 M-x multiply-by-sevenとタイプしてRETを押せば、このコードを使うことができる。 ステータスバーには、 `The result is ...'に続けて乗算結果が表示されるはずである。

より一般的には、このような関数を起動する方法は2つある。

  1. C-u 3 M-x backward-charのように、関数に渡すべき数を含む前置引数をタイプしてから、 M-xに続けて関数名をタイプする。あるいは、
  2. C-u 3 C-bのような関数にバインドされたキー列をタイプする。

上のキー列の例は、どちらもポイントを文字単位に3つ戻る (multiply-by-sevenにはキーがバインドされていないので、キーバインドを使う例としては使えない)。

前置引数を対話的関数に渡すには、M-3 C-bのようにキーMETAに続けて数をタイプするか、C-u 3 C-bのように C-uに続けて数をタイプする(数をタイプせずにC-uだけをタイプすると、デフォルトは4)。

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

3.3.1 対話的 multiply-by-seven

スペシャルフォームinteractiveと関数messageの使い方を multiply-by-sevenで見てみよう。関数定義はつぎのとおりであった。

(defun multiply-by-seven (number)       ; 対話的版
  "Multiply NUMBER by seven."
  (interactive "p")
  (message "The result is ~d" (* 7 number)))

この関数では、式(interactive "p")は、要素2個のリストである。"p"は、 前置引数を関数に渡すことをxyzzyに指示するもので、その値を関数への引数として使う。

引数は数である。つまり、つぎの行のシンボルnumberには数が束縛される。

(message "The result is ~d" (* 7 number))

たとえば、前置引数が5であったとすると、 Lispインタープリタはつぎのような行であるとして評価する (messeage関数を評価する場合、*scratch*にコードをコピーし、C-jとタイプする。 C-x C-eだと`t'と表示されるだけである)。

(message "The result is ~d" (* 7 5))

まず、インタープリタは内側のリスト(* 7 5)を評価する。値35が返される。つぎに、 インタープリタは外側のリストを評価するが、それには、リストの第2要素以降の値を関数messageに渡す。

すでに説明したように、messageはユーザーに1行のメッセージを表示するために考えられた xyzzy Lispの関数である(See 節 1.8.5 関数message)。すなわち、関数messageは、 `~d'や`~s'や`~c'を除いて、第1引数を字面のとおりにステータスバーに表示する。 `~d'や`~s'や`~c'の 制御文字列があると、 2番目以降の引数を調べてその値を対応する制御文字列の位置に表示する。

対話的関数multiply-by-sevenでは、制御文字列としては`~d'を使っており、これは数を必要とする。 (* 7 5)を評価した結果は数35である。したがって、`~d'の位置に数35が表示されるので、 メッセージは`The result is 35'となる。

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

3.4 interactiveの他のオプション

上の例のmultiply-by-sevenでは、interactiveの引数として"p"を用いた。 この引数は、ユーザーがタイプしたC-uMETAに続く数を、 関数への引数として渡すコマンドとして解釈するようにxyzzyに指示する。xyzzyでは、あらかじめ定義された文字を interactiveに指定できる。ほとんどの場合、これらのオプションを指定すれば、 必要な情報を関数へ対話的に渡せる

たとえば、文字`r'を指定すると、xyzzyは、リージョンの開始位置と終了位置(ポイントとマークの現在値) を2つの引数として関数へ渡す。つぎのように使う。

(interactive "r")

一方、`B'を指定すると、xyzzyは関数にバッファ名を渡す。`B'をみると、xyzzyは、"BAppend to buffer: "のような `B'に続く文字列をプロンプトとしてミニバッファに表示して、ユーザーに名前を問い合わせる。xyzzyはプロンプト を表示するだけでなく、TABが押されると名前を補完する。

2つ以上の引数を取る関数では、interactiveに続く文字列に要素を追加すれば各引数に情報を渡せる。 このとき、各引数に情報を渡す順番は、リストinteractiveに指定した順番と同じである。 文字列の各部分は、改行`\n'で区切る。たとえば、"BAppend to buffer: "に続けて、`\n'と`r'を指定する。 こうすると、xyzzyはバッファ名を問い合わせることに加えて、ポイントとマークの値を関数に渡す。つまり、 引数は全部で3つである。

この場合、関数定義はつぎのようになり、bufferstartendの 各シンボルには、バッファ、リージョンの開始位置、終了位置の現在値をinteractiveが束縛する。

(defun 関数名 (buffer start end)
  "説明文..."
  (interactive "BAppend to buffer: \nr")
  関数の本体...)

引数がない関数の場合には、interactiveには何も指定しなくてよい。そのような関数では、 単に式(interactive)を指定する。関数mark-whole-bufferは、このようになっている。

読者のアプリケーションにおいて、あらかじめ定義した文字では不十分な場合には、 interactiveにリストを指定すれば、独自の引数を渡せる。

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

3.5 コードの恒久的インストール

関数定義を評価して関数定義をインストールすると、xyzzyを終了するまでは関数定義は有効である。 新たにxyzzyを起動したときには、関数定義を再度評価するまでは関数定義はインストールされない。

新たにxyzzyを起動するたびに、自動的にコードをインストールしたくなるであろう。これにはいくつかの方法がある。

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

3.6 let

let式はLispのスペシャルフォームであり、ほとんどの関数定義で使う必要がある。

letはシンボルに値を結び付ける、すなわち、束縛するのであるが、 関数の内部と外部で同じ名前の変数を使ってもLispインタープリタが混乱しないような方法でこれを行う。

このスペシャルフォームが必要な理由を理解するために、「家を塗装し直す必要がある」などのように一般的に 「家」と呼ぶような家屋を所有している状況を仮定してみよう。読者が友人宅を訪問したときに、友人が「家」 といったときには、 友人の家を意味しているのであって、読者の家ではないだろう。

友人は彼の家を意味しているのに、読者が読者の家を意味していると考えると、混乱が生じる。 ある関数の内部で使う変数と別の関数の内部で使う変数が同じ名前でも、同じ値を参照する意図がないのであれば、 Lispでも同じことが起こる。letはこの種の混乱を防ぐ.

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

letで混乱を避ける

スペシャルフォームletはこの種の混乱を防ぐ。letは、let式の外側にある 同じ名前を隠すようなローカル変数(local variable)としての名前を作り出す。これは、 友人が「家」といった場合、彼は読者のではなく彼の家を意味していると理解することに似ている (引数リストに使われるシンボルも同じように働く。

let式が作るローカル変数は、let式の内側(とlet式から呼ばれた式の内側) でのみそれらの値を保持する。ローカル変数は、let式の外側にはまったく影響しない。

letは一次的なローカルのsetqのようなものとも理解できるだろう. letにより定義された値はletが終わると自動的に解除されます. letはその境界の中でだけ有効になるのです.

letでは一度に複数個の変数を作れる。また、letで各変数を作るときには、 指定した値かnilを初期値として設定できる(専門用語では、「変数に値を束縛する」という)。 letで変数を作って束縛すると、letの本体のコードを実行し、 let式全体の値として本体の最後の式の値を返す(「実行(execute)」とは、 リストを評価することを意味する専門用語である。これは、「実質的な効果を与える」という単語の用法 (Oxford English Dictionary)からきている。ある動作を行わせるために式を評価するので、 「実行(execute)」は「評価(evaluate)」の同義語である)。

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

3.6.1 let式の構造

let式は、3つの要素からなるリストである。第一の部分は、シンボルletである。 第二の部分は、変数リスト(varlist)と呼ばれるリストであり、その個々の要素は、 単独のシンボルであるか、第1要素がシンボルであるような 2要素リストである。let式の第3の部分は、 letの本体である。通常、本体は複数個のリストである。

 (let varlist body...)

変数リストの各シンボルは、スペシャルフォームletで初期値を設定された変数である。 単独のシンボルの場合、初期値はnilである。第1要素がシンボルであるような2要素リストである場合、 そのシンボルには、Lispインタープリタが第2要素を評価した結果を束縛する。

したがって、変数リストは(thread (needles 3))のようになる。このlet式では、 xyzzyは、シンボルthreadには初期値nilを、シンボルneedlesには初期値3を束縛する。

let式を書くときには、let式の雛型の適当な項目に必要な式を書き込めばよい。

変数リストが2要素リストだけから成る場合には、let式の雛型はつぎのようになる。

(let ((variable value)
      (variable value)
      ...)
  body...)

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

3.6.2 let式の例

つぎの式では、2つの変数zebratigerを作り、それぞれに初期値を与える。 let式の本体は、関数messageを呼ぶリストである。

(let ((zebra 'stripes)
      (tiger 'fierce))
  (message "One kind of animal has ~s and another is ~s."
           zebra tiger))

変数リストは((zebra 'stripes) (tiger 'fierce))である。

2つの変数は、zebratigerである。各変数は2要素リストの先頭要素であり、 個々の値は2要素リストの第2要素である。変数リストでは、xyzzyは、変数zebraには 値stripesを、変数tigerには値fierceを束縛する。この例では、 どちらの値も引用符を直前に付けたシンボルである。これらの値は、リストであっても文字列であってもよい。 変数を保持するリストのあとには、let式の本体が続く。この例では、ステータスバーに 文字列を表示する関数messageを使ったリストが本体である。

これまでのように、式を*scratch*バッファにコピーして、例の最後の括弧の直後にカーソルを置いて C-jとタイプすれば例を評価できる。そうすると、ステータスバーにはつぎのように 表示されるはずである。

One kind of animal has stripes and another is fierce.

これまでに見てきたように、関数messageは `~s'を除いて第1引数を表示する。この例では、 変数zebraの値が最初の`~s'の位置に、変数tigerの値が2番目の`~s'の位置に表示される。

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

3.6.3 let式の非初期化変数

let式において特に初期値を束縛していない変数には、自動的に初期値として nilを束縛する。つぎの例を見てほしい。

(let ((birch 3)
      pine
      fir
      (oak 'some))
  (message
   "Here are ~d variables with ~s, ~s, and ~s value."
   birch pine fir oak))

変数リストは((birch 3) pine fir (oak 'some))である。

先ほどのようにこの式を評価すると、ステータスバーにはつぎのように表示される。

Here are 3 variables with nil, nil, and some value.

この例では、xyzzyは、シンボルbirchに数3を、シンボルpinefirnilを、シンボルoaksomeを束縛する。

letの最初の部分では、変数pinefirは括弧で囲んでない単独のアトムである。 そのため、これらの変数は空リストnilに束縛される。一方、oakは、 リスト(oak 'some)の一部なので、someに束縛される。同様に、 birchもリストの一部なので数3に束縛される(数はそれ自身に評価されるので、 数をクオートする必要はない。また、メッセージに数を表示するには`~s'のかわりに`~d'を使う)。 4つの変数をまとめてリストにすることで、letの本体と区別できるようにする。

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

3.7 スペシャルフォームif

defunletに続く3番目のスペシャルフォームは、条件分岐ifである。 このフォームは、コンピュータに判定を指示する。ifを使わずに関数定義を書くことも可能であろうが、 多くの場面で使用する重要なものなので、ここで説明しておこう。

ifの基本的な考え方は、「もし(if)条件が真ならば(then)式を評価する」 である。条件が真でなければ、式を評価しない。たとえば、「もし(if)暑くて夏ならば(then)海へ行く!」 のような判定に使う。

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

if の詳細

Lispでif式を書く場合には、「then」を書かない。第1要素がifであるリストの 第2要素と第3要素のそれぞれに、判定条件と真の場合の動作を指定する。if式の条件を調べる部分を 判定条件(if-part)、 2番目の引数を真の場合の動作(then-part)と呼ぶ。

また、if式を書くとき、判定条件はシンボルifと同じ行に書くが、 真の場合の動作は2行目以降に書く。このようにするとif式が読みやすくなる。

(if 判定条件
    真の場合の動作)

判定条件は、Lispインタープリタが評価できればどんな式でもよい。

いつものようにして評価できる例をつぎにあげよう。判定条件は、「数5は数4よりも大きいか」である。 これは真なので、メッセージ`5 is greater than 4!'が表示される。

 (if (> 5 4)                            ; 判定条件
    (message "5 is greater than 4!"))   ; 真の場合の動作

(関数>は、第1引数が第2引数よりも大きいかどうかを調べ、そうならば真を返す。)

実際のコードでは、if式の判定条件は、式(> 5 4)のように固定されていない。 判定条件に使われる少なくとも1つの変数に束縛された値は、あらかじめわかっていないはずである (あらかじめ値がわかっていれば、テストする必要はない)。

たとえば、関数定義の引数に束縛された値を使う。つぎの関数定義では、関数に渡される値は動物の性質である。 characteristicに束縛された値がfierce(獰猛な)の場合には、 メッセージ`It's a tiger!'を表示する。そうでなければ、nilを返す。

  (defun type-of-animal (characteristic)
     "Print message in echo area depending on CHARACTERISTIC.
   If the CHARACTERISTIC is the symbol `fierce',
   then warn of a tiger."
     (if (equal characteristic 'fierce)
         (message "It's a tiger!")))

これまでのように関数定義を評価して定義をxyzzyにインストールし、つぎの2つの式を評価して結果を確認できる。

(type-of-animal 'fierce)

(type-of-animal 'zebra)

(type-of-animal 'fierce)を評価すると、ステータスバーにはメッセージIt's a tiger!が表示される。 (type-of-animal 'zebra)を評価すると、ステータスバーにはnilと表示される。

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

3.7.1 関数type-of-animalの詳細

関数type-of-animalを詳しく見てみよう。

type-of-animalの関数定義は、関数定義の雛型とif式の雛型を埋めて書いたものである。

これらは対話的関数の雛型ではない。

(defun 関数名 (引数リスト)
  "説明文..."
  本体...)

この雛型に対応する関数の部分はつぎのとおりである。

(defun type-of-animal (characteristic)
  "Print message in echo area depending on CHARACTERISTIC.
If the CHARACTERISTIC is the symbol `fierce',
then warn of a tiger."
  本体(if式))

関数名はtype-of-animalであり、渡される引数は1つである。引数リストのあとには複数行の 説明文字列が続いている。各関数定義に説明文を付加しておくのはよい習慣なので、この例でも説明文を付けておいた。 関数定義の本体はif式から成る。

if式の雛型はつぎのとおりである。

  (if 判定条件
    真の場合の動作)

関数type-of-animalifのコードはつぎのとおりである。

(if (equal characteristic 'fierce)
    (message "It's a tiger!")))

ここで、判定条件はつぎのとおり。

(equal characteristic 'fierce)

Lispでは、equalは、第1引数が第2引数に等しいかどうかを調べる関数である。 第2引数はクオートしたシンボル'fierceであり、第1引数はシンボルcharacteristicの値、 つまり、この関数に渡された引数である。

type-of-animalの最初の使用例では、引数fiercetype-of-animalに渡した。 fiercefierceに等しいので、式(equal characteristic 'fierce)は真を返す。 すると、ifは第2引数、つまり、真の場合の動作(message "It's a tiger!")を評価する。

一方、type-of-animalの2番目の使用例では、引数zebratype-of-animalに 渡した。zebrafierceに等しくないので、真の場合の動作は評価されず、 if式はnilを返す。

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

3.8 if--then--else式

if式には第3引数を指定することもでき、判定条件が偽の場合の動作(else-part)である。 判定条件が偽であると、if式の第2引数、つまり、真の場合の動作はいっさい評価されず、第3引数、 つまり、偽の場合の動作が評価される。曇の場合を考慮して「もし(if)暑くて夏ならば(then)海へ行く、 そうでなければ(else)読書する!」のような判定である。

Lispのコードには「else」を書かない。偽の場合の動作は、if式の真の場合の動作のうしろに書く。 偽の場合の動作は新しい行で始め、真の場合の動作よりも字下げを少なくする。

(if true-or-false-test
    action-to-carry-out-if-the-test-returns-true
  action-to-carry-out-if-the-test-returns-false)

たとえば、つぎのif式では、いつものように評価するとメッセージ `4 is not greater than 5!'を表示する。

(if (> 4 5)                             ; 判定条件
    (message "5 is greater than 4!")    ; 真の場合の動作
  (message "4 is not greater than 5!")) ; 偽の場合の動作

適当に字下げすると真の場合の動作と偽の場合の動作を区別しやすくなることに注意してほしい(xyzzyには、 if式を自動的に正しく字下げするコマンドがある。

if式に偽の場合の動作を追加するだけで、関数type-of-animalの機能を拡張できる。

関数type-of-animalのつぎの版を評価して定義をインストールしてから、続く2つの式を評価するとこの拡張を理解できるであろう。

   (defun type-of-animal (characteristic)  ; 第2版.
  "Print message in echo area depending on CHARACTERISTIC.
If the CHARACTERISTIC is the symbol `fierce',
then warn of a tiger;
else say it's not fierce."
  (if (equal characteristic 'fierce)
      (message "It's a tiger!")
    (message "It's not fierce!")))

上記の式を評価したら、下の2つの式を評価してほしい

(type-of-animal 'fierce)

(type-of-animal 'zebra)

(type-of-animal 'fierce)を評価すると、ステータスバーにメッセージIt's a tiger!が表示される。 ところが、(type-of-animal 'zebra)を評価するとIt's not fierce!と表示される。

(characteristicがferocious(凶暴な)であれば、メッセージIt's not fierce!が 表示されるが、これは誤解を招く。コードを書く際には、ifで調べる値の可能な組み合わせを十分に 考慮し、そのようにプログラムを書く必要がある。)

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

3.9 xyzzy Lispの真偽値

if式での判定条件が真かどうかの検査には重要な側面がある。これまで、述語の値としての 「真(true)」と「偽(false)」を、新たなxyzzy Lispオブジェクトであるかのように使ってきた。 実際には、「偽(false)」とは、すでに馴染みのあるnilのことである。これ以外は、 たとえ何であれ、「真(true)」である。

判定条件の式では、評価結果がnil以外の値であれば、真(true)と解釈する。いいかえれば、 47などの数、"hello"のような文字列、(nilではない)flowersなどのシンボルやリスト、バッファでさえも、 真と解釈する。

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

nil について

これらの例を示すまえに、nilについて説明しておこう。

xyzzy Lispでは、シンボルnilには2つの意味がある。第一に、空リストを意味する。第二に、 偽を意味し、判定条件が偽の場合に返される値でもある。nilは、空リスト()とも nilとも書ける。Lispインタープリタにとっては、()nilも同じである。 一方、人間向きには、偽はnilと、空リストは()と書く傾向がある。

xyzzy Lispでは、nilでない、つまり、空リストでない値は真と解釈する。つまり、 評価結果が空リスト以外であれば、if式の判定条件は真になる。たとえば、判定条件に数を書いた場合、 数を評価するとその数そのものである。したがって、if式の判定条件は真になる。 式の評価結果がnilつまり空リストの場合に限り、判定条件は偽になる。

つぎの2つの式を評価すると理解できるだろう。

最初の例では、if式の判定条件として数4を評価するが、その結果は数4である。したがって、 式の真の場合の動作が評価され、その結果が返される。つまり、ステータスバーには`true'と表示される。 2番目の例では、nilは偽を意味するので、偽の場合の動作が評価されその結果が返される。 ステータスバーには`false'と表示される。

(if 4
    'true
  'false)

(if nil
    'true
  'false)

判定結果として真を表す有用な値がない場合には、Lispインタープリタは真としてシンボルtを返す。 たとえば、つぎの例でわかるように、式(> 5 4)を評価するとtを返す。

(> 5 4)

一方、偽の判定結果としては、この関数はnilを返す。

(> 4 5)

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

3.10 save-excursion

関数save-excursionは、本章で説明する4番目で最後のスペシャルフォームである。

エディタとしてのxyzzy Lispプログラムでは、関数save-excursionを多用している。この関数は、 ポイントとマークの位置を記録してから、関数の本体を実行し、ポイントやマークの位置が移動していれば 実行前の状態に復元する。この関数の主要な目的は、ポイントやマークの予期しない移動によってユーザーが混乱したり 煩わされないようにすることである。

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

ポイントとマーク

save-excursionを説明するまえに、xyzzyのポイントとマークについて復習しておこう。 ポイント(point)とは、カーソルの現在位置である。カーソルがどこにあろうとも、 それがポイントである。端末画面上では、カーソルは文字に重なって表示されるが、ポインタはその文字の直前にある。 xyzzy Lispでは、ポイントは整数である。バッファの最初の文字は0、つぎの文字は1と数える。 関数pointはカーソルの現在位置を数で返す。各バッファごとに、個別のポイントがある。

マーク(mark)も、バッファ内の位置を表す。C-SPCset-mark-command) などのコマンドで、値を設定する。マークを設定してあれば、コマンドC-x C-xexchange-point-and-mark)を用いてカーソルをマークに移動するとともに、カーソル移動前の ポイント位置にマークを設定する。

バッファのポイントとマークのあいだの部分をリージョン(region)と呼ぶ。 center-regionkill-regionなどのさまざまなコマンドはリージョンに作用する。

スペシャルフォームsave-excursionは、ポイントとマークの位置を記録し、 Lispインタープリタがスペシャルフォームの本体のコードを評価し終えると、それらの位置を復元する。したがって、 テキストの始めの部分にポイントがあったときに、コードでポイントをバッファの最後に移動したとすると、 save-excursionは、関数の本体の式を評価し終えるとポイントをもとの場所に戻す。

xyzzyでは、ユーザーが意図しなくても、関数の内部動作の過程でポイントを移動することが多い。たとえば、 indent-regionはポイントを移動する。(ユーザーの視点からは)予期しないような不必要なポイントの 移動でユーザーが混乱しないように、ポイントやマークがユーザーの期待どおりの位置にあるように save-excursionを多用する。save-excursionを使うと、正しく管理できる。

正しく管理できるように、save-excursionの内側のコードで何か不都合なことが起こった場合 (専門用語でいえば、「異常終了した場合」)でも、save-excursionはポイントとマークの値を復元する。 この機能はとても役に立つ。

ポイントとマークの値を記録することに加えて、save-excursionは、カレントバッファも記録しておいて 復元する。つまり、バッファを切り替えるようなコードを書いた場合でも、save-excursionにより もとのバッファに戻れる。

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

3.10.1 save-excursion式の雛型

save-excursionを使うコードの雛型は簡単である。

(save-excursion
  本体...)

関数の本体は、複数個の式であり、Lispインタープリタはそれらを順に評価する。本体に複数個の式がある場合、 最後の式の値が関数save-excursionの値として返される。本体のそれ以外の式は、 副作用を得るためだけに評価される。save-excursion自体も(ポイントとマークの位置を復元するという) 副作用を得るためだけに使われる。

save-excursion式の雛型をより詳しく書くと、つぎのようになる。

(save-excursion
  本体の最初の式
  本体の2番目の式
  本体の3番目の式
   ...
  本体の最後の式)

ここで、式は単一のシンボルやリストである。

xyzzy Lispのコードでは、save-excursion式はlet式の本体に現れることが多い。つぎのようになる。

  (let 変数リスト
    (save-excursion
      本体...))

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

3.11 復習

これまでの章では、多数の関数やスペシャルフォームを紹介してきた。以下には、 説明しなかった同種の関数も含めて概要を記しておく。

eval-last-sexp

ポイントの現在位置の直前にあるシンボリック式を評価する。引数を指定せずにこの関数を起動した場合には、 ステータスバーに値を表示する。引数を指定した場合には、カレントバッファに結果を表示する。 このコマンドは慣習的にC-x C-eにバインドされる。

defun

関数を定義する。このスペシャルフォームは、多くても5つの部分から成る。つまり、名前、 関数に渡される引数の雛型、説明文、省略してもよい対話的使用の宣言、定義の本体である。

例:

(defun back-to-indentation ()
  "Move point to first visible character on line."
  (interactive)
  (beginning-of-line 1)
  (skip-chars-forward " \t"))
interactive

対話的に使える関数であることをインタープリタに対して宣言する。このスペシャルフォームには、 関数の引数に渡すべき情報を指定する文字列を続けてもよい。これらの文字列には、インタープリタが使用する プロンプトも指定できる。文字列の各要素は改行`\n'で区切る。

よく使うコード文字はつぎのとおりである。

b 既存バッファの名前。

f 既存ファイルの名前

p 数値の前置引数(「p」は小文字)。

r 2つの数値引数でポイントとマークを渡す。値が小さいほうを先に渡す。これは、1つではなく2つの引数を渡す唯一のコード文字である。

let

letの本体で使用する変数のリストを宣言し、それらにnilや指定した値を 初期値として設定する。続いて、letの本体の式を評価し、その最後の値を返す。 letの本体の内側では、Lispインタープリタはletの外側で同じ名前の変数に束縛された値を 使うことはない。

例:

(let ((foo (buffer-name (selected-buffer)))
      (bar (buffer-size)))
  (message
   "This buffer is ~s and has ~d characters."
   foo bar))
save-excursion

このスペシャルフォームの本体を評価するまえに、ポイントとマークの値、カレントバッファを記録する。 そのあとで、ポイントとマークの値、バッファを復元する。

例:

(message "We are ~d characters into this buffer."
         (- (point)
            (save-excursion
              (goto-char (point-min)) (point))))
if

スペシャルフォームifは、条件判定(conditional)である。 xyzzyには別の条件判定もあるが、もっともよく使うのはifであろう。

equal
eq

2つのオブジェクトが同じであるかどうかを調べる。equalは「同じ」という 意味で使うがeqはそうではない.equalは、2つのオブジェクトが同じ本のコピーのように, 同じ内容で同じ構造ならば真を返す。一方、eqは、2つの引数が同一のオブジェクトならば真を返す。

<
>
<=
>=

関数<は、第1引数が第2引数より大きいかどうかを検査する。対応する関数>は、 第一引数が第二引数より大きいかどうかを検査する。同様に、<=は、 第1引数が第2引数より小さいか等しいかどうかを検査し、>=は、第一引数が第二引数より大きいか 等しいかどうかを検査する。いずれの場合でも、2つの引数は数かマーカー(バッファの位置を示すマーカー) である必要がある。

string<
string-lessp
string>
string-greaterp
string<=
string-not-greaterp
string>=
string-not-lessp
string=
string-equal

関数string<は最初の引数が二番目の引数よりも小さいかどうかを確認します。

string<の引数は文字列かシンボルでなければなりません. 順序は辞書的に付けられます。シンボルの名前はシンボル自身の代わりとして使われます.

`""' のような空文字は文字のない文字列ですので,どんな文字よりも小さくなります.

string-lesspstring<とほぼ同じですが、大文字小文字を区別しないところが違います

string=は一致するかの確認を行います.

string-equalstring=とほぼ同じですが、大文字小文字を区別しません。

その他のstring<=string-not-greaterpなどの関数はリファレンスを 参照して下さい。(リファレンスを見ないでも、どのような動作をするかは想像できるでしょう)

message

ステータスバーにメッセージを表示する。第1引数は文字列であり、文字列に続く引数の値を表示するために `~s'や`~d'や`~c'を含んでもよい。`~s'で使う引数は文字列かシンボルであること。`~d'で使う引数は数であること。 `~c'で使う引数も数(アスキーコード)であるが、その値のASCIIコードの文字として表示される。

setq
set

関数setqは、第2引数の値を第1引数の値として設定する。第1引数は自動的にクオートされる。 連続する2つの引数ごとに同じことを行う。もう一方の関数setは、2つの引数のみを取り、 両者を評価してから、第2引数の値を第1引数の値として設定する。

buffer-name

バッファ名を文字列として返す。

get-buffer-file-name

バッファが訪問しているファイル名を返す。

selected-buffer

xyzzyが操作対象としているバッファを返す。このバッファが画面に表示されているとは限らない。

other-buffer

other-bufferに引数として渡したバッファやカレントバッファ以外の)もっとも最近に選択していたバッファを返す。

switch-to-buffer

xyzzyが操作対象とするバッファを指定し、同時に、カレントウィンドウに表示してユーザーが見られるようにする。通常、C-x bにバインドされる。

set-buffer

xyzzyが操作対象とするバッファを切り替える。ウィンドウの表示は変更しない。

buffer-size

カレントバッファ内にある文字数を返す。

point

バッファの先頭から現在のカーソル位置までの文字の個数を表す整数を返す。

point-min

カレントバッファで取りえるポイントの最小値を返す。ナロイングしていない場合には、0である。

point-max

カレントバッファで取りえるポイントの最大値を返す。ナロイングしていない場合には、バッファの最後である。

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

3.12 演習問題

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


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