[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3. 関数定義の書き方

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Writing%20Defuns"
"intro/関数定義の書き方"へのコメント(無し)

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

An Aside about Primitive Functions

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Primitive%20Functions"
"intro/AnAsideaboutPrimitiveFunctions"へのコメント(無し)

すべての関数は別の関数を用いて定義されているが、 プログラミング言語Cで書かれた少数の 基本操作(primitive)関数はそうではない。 関数の定義を書くときには、他の関数を構成部品として用いてEmacs Lispで書く。 そのとき使用する関数は、 それ自身Emacs Lispで(読者自身が)書いたものであったり、 Cで書かれた基本操作関数である。 基本操作関数は、Emacs Lispで書いたものとまったく同じように使え、 そのように動作する。 これらをCで書いてあるのは、Cが動く十分な能力を持ったコンピュータならば 容易にGNU Emacsを動作できるようにするためである。

強調しておくが、Emacs Lispでコードを書くとき、 Cで書いた関数の使い方とEmacs Lispで書いた関数の使い方とは区別しない。 両者の違いは無関係なのである。 わざわざ言及したのは、興味深いと考えたからである。 既存の関数がEmacs Lispで書いてあるのか、Cで書いてあるのかは、 特に調べない限りわからない。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.1 スペシャルフォームdefun

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=defun"
"intro/スペシャルフォームdefun"へのコメント(無し)

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

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

関数定義は、単語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)。

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

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

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

 
(multiply-by-seven 3)

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

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

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Install"
"intro/関数定義のインストール"へのコメント(無し)

EmacsのInfoで読んでいる場合には、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を評価すると、Emacsにmultiply-by-sevenをインストール したことになる。 これで、forward-wordや編集関数などと同じく、 この関数もEmacsの一部である (Emacsを終了するまで、multiply-by-sevenはインストールされたままである。 Emacs起動時に自動的にコードをロードするには 3.5 コードの恒久的インストールを参照)。

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

 
(multiply-by-seven 3)

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

 
multiply-by-seven:
Multiply NUMBER by seven.

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

3.2.1 関数定義の変更    How to change a function definition.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.2.1 関数定義の変更

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Change%20a%20defun"
"intro/関数定義の変更"へのコメント(無し)

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

例として、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行以上にわたる注釈は、各行をセミコロンで始める。

注釈に関してより詳しくは、 節 `Comments' in

GNU Emacs Lisp リファレンスマニュアル
や See 節 16.3 ファイル`.emacs'入門。

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.3 関数を対話的にする

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Interactive"
"intro/関数を対話的にする"へのコメント(無し)

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

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

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 forward-sentenceのように、 関数に渡すべき数を含む前置引数をタイプしてから、 M-xに続けて関数名をタイプする。 あるいは、

  2. C-u 3 M-eのような関数にバインドされたキー列をタイプする。

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

(コマンドをキーにバインドする方法については See 節 16.7 キーバインド例。)

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

3.3.1 対話的multiply-by-seven    The interactive version.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.3.1 対話的multiply-by-seven

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=multiply-by-seven%20in%20detail"
"intro/対話的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"は、前置引数を関数に渡すことをEmacsに指示するもので、 その値を関数への引数として使う。

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

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

たとえば、前置引数が5であったとすると、 Lispインタープリタはつぎのような行であるとして評価する (GNU Emacsで読んでいる場合には、読者自身がこの式を評価してほしい)。

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

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

すでに説明したように、messageはユーザーに1行のメッセージを 表示するために考えられたEmacs 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'となる。

(関数multiply-by-sevenを呼んだときは、 メッセージには二重引用符が付かないが、messageを呼んだときには、 二重引用符のあいだにテキストが表示されることに注意してほしい。 messageを先頭要素とする式を評価したときには、 messageが返した値がエコー領域に表示されるが、 関数内で用いた場合には、副作用としてmessageが二重引用符なしでテキストを 表示するからである。)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.4 interactiveの他のオプション

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Interactive%20Options"
"intro/interactiveの他のオプション"へのコメント(無し)

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

GNU Emacs Lispリファレンスマニュアル
)。

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

 
(interactive "r")

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

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

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

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

(プロンプトのコロンのうしろの空白は、 プロンプトを表示したときに見やすくするためのものである。 関数append-to-bufferでもこのようにしている。 @xref{append-to-bufferm}。)

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

読者のアプリケーションにおいて、あらかじめ定義した文字では不十分な場合には、 interactiveにリストを指定すれば、独自の引数を渡せる。 この技法について詳しくは、See 節 `Using Interactive' in

GNU Emacs Lispリファレンスマニュアル



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

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

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Permanent%20Installation"
"intro/コードの恒久的インストール"へのコメント(無し)

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

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

最後に、Emacsのすべてのユーザーが使いそうなコードならば、 ネットワークに投稿するかFree Software Foundationに送付する (こうする場合には、投稿するまえにコードに コピーレフトの注意書きを入れてほしい)。 Free Software Foundationにコードを送付すると、 Emacsの次期リリースにコードが含まれるかもしれない。 このような「寄贈行為」によりEmacsが成長してきたのである。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.6 let

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=let"
"intro/let"へのコメント(無し)

let式はLispのスペシャルフォームであり、 ほとんどの関数定義で使う必要がある。 letはよく使うので、本節で説明しておく。

letはシンボルに値を結び付ける、すなわち、束縛するのであるが、 関数の内部と外部で同じ名前の変数を使ってもLispインタープリタが混乱しない ような方法でこれを行う。 このスペシャルフォームが必要な理由を理解するために、 「家を塗装し直す必要がある」などのように一般的に「家」と呼ぶような 家屋を所有している状況を仮定してみよう。 読者が友人宅を訪問したときに、友人が「家」といったときには、 友人の家を意味しているのであって、読者の家ではないだろう。 友人は彼の家を意味しているのに、読者が読者の家を意味していると考えると、 混乱が生じる。 ある関数の内部で使う変数と別の関数の内部で使う変数が同じ名前でも、 同じ値を参照する意図がないのであれば、Lispでも同じことが起こる。

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

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

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

Oxford English Dictionary
)からきている。 ある動作を行わせるために式を評価するので、 「実行(execute)」は「評価(evaluate)」の同義語である)。

3.6.1 let式の構造   
3.6.2 let式の例   
3.6.3 let式の非初期化変数   



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.6.1 let式の構造

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Parts%20of%20let%20Expression"
"intro/let式の構造"へのコメント(無し)

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

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

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

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

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

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

 
(let ((変数 )
      (変数 )
      ...)
      本体...)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.6.2 let式の例

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Sample%20let%20Expression"
"intro/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要素である。 変数リストでは、Emacsは、変数zebraには値stripesを、 変数tigerには値fierceを束縛する。 この例では、どちらの値も引用符を直前に付けたシンボルである。 これらの値は、リストであっても文字列であってもよい。 変数を保持するリストのあとには、let式の本体が続く。 この例では、エコー領域に文字列を表示する関数messageを使ったリストが 本体である。

これまでのように、例の最後の括弧の直後にカーソルを置いてC-x C-eと タイプすれば例を評価できる。 そうすると、エコー領域にはつぎのように表示されるはずである。

 
"One kind of animal has stripes and another is fierce."

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.6.3 let式の非初期化変数

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Uninitialized%20let%20Variables"
"intro/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."

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.7 スペシャルフォームif

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=if"
"intro/スペシャルフォームif"へのコメント(無し)

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

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

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!")))

GNU Emacsで読んでいる場合には、これまでのように関数定義を評価して 定義をEmacsにインストールし、つぎの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の詳細    An example of an if expression.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.7.1 関数type-of-animalの詳細

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=type-of-animal%20in%20detail"
"intro/関数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-animalの実際のifのコードはつぎのとおりである。

 
(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を返す。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.8 If--then--else式

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=else"
"intro/If--then--else式"へのコメント(無し)

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

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

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

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

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

適当に字下げすると真の場合の動作と偽の場合の動作を 区別しやすくなることに注意してほしい (GNU Emacsには、if式を自動的に正しく字下げするコマンドがある。 See 節 1.1.3 GNU Emacsのリスト入力補佐機能)。

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!")))

 
(type-of-animal 'fierce)

(type-of-animal 'zebra)

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.9 Lispの真偽値

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Truth%20&%20Falsehood"
"intro/Lispの真偽値"へのコメント(無し)

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

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

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

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

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)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.10 save-excursion

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=save-excursion"
"intro/save-excursion"へのコメント(無し)

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

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

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

マーク(mark)も、バッファ内の位置を表す。 C-SPC(set-mark-command)などのコマンドで、値を設定する。 マークを設定してあれば、 コマンドC-x C-xexchange-point-and-mark)を用いて カーソルをマークに移動するとともに、 カーソル移動前のポイント位置にマークを設定する。 さらに、別のマークが設定してあった場合には、 交換前のマークの位置をマークリングに保存する。 このようにして複数個のマーク位置を保存できる。 C-u C-SPCと数回タイプすると保存したマーク位置に移動できる。

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

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

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

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

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

3.10.1 save-excursion式の雛型    One slot to fill in.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.10.1 save-excursion式の雛型

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Template%20for%20save-excursion"
"intro/save-excursion式の雛型"へのコメント(無し)

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

 
(save-excursion
  本体...)

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

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

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

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

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

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.11 復 習

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Review"
"intro/復 習"へのコメント(無し)

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

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

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

例:

 
(defun back-to-indentation ()
  "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つの引数を渡す唯一のコード文字である。

コード文字の完全な一覧に ついては、See 節 `Code Characters for `interactive'' in

GNU Emacs Lispリファレンスマニュアル

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

例:

 
(let ((foo (buffer-name))
      (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
関数の第1引数を評価する。 それが真ならば、第2引数を評価する。 そうでない場合、第3引数があればそれを評価する。

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

例:

 
(if (string= (int-to-string 19)
             (substring (emacs-version) 10 12))
    (message "This is version 19 Emacs")
  (message "This is not version 19 Emacs"))

equal
eq
2つのオブジェクトが同じであるかどうかを調べる。 equalは、2つのオブジェクトが同じ内容で同じ構造ならば真を返す。 一方、eqは、2つの引数が同一のオブジェクトならば真を返す。

<
>
<=
>=
関数<は、第1引数が第2引数より小さいかどうかを検査する。 対応する関数>は、第1引数が第2引数より大きいかどうかを検査する。 同様に、<=は、第1引数が第2引数より小さいか等しいかどうかを検査し、 >=は、第1引数が第2引数より大きいか等しいかどうかを検査する。 いずれの場合でも、2つの引数は数である必要がある。

message
エコー領域にメッセージを表示する。 メッセージは1行であること。 第1引数は文字列であり、文字列に続く引数の値を表示するために `%s'や`%d'や`%c'を含んでもよい。 `%s'で使う引数は文字列かシンボルであること。 `%d'で使う引数は数であること。 `%c'で使う引数も数であるが、 その値のASCIIコードの文字として表示される。

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

buffer-name
引数はなく、バッファ名を文字列として返す。

buffer-file-name
引数はなく、バッファが訪問しているファイル名を返す。

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

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

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

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

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

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

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

3.12 演習問題

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=defun%20Exercises"
"intro/演習問題"へのコメント(無し)


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]