[Prev] [Index] [Next] [上端へ移動] [下端へ移動].
更新日時:2008年05月10日
リストを評価するとき、Lispインタープリタは、リストの先頭のシンボルに関数定義が結び付けられているか どうかを調べる。いいかえれば、シンボルが関数定義を指すかどうかを調べる。そうならば、 コンピュータは定義内の命令列を実行する。関数定義を持つシンボルを、単に関数と呼ぶ (しかし、正確には、定義が関数であり、シンボルはそれを指すだけである)。
Lispでは、mark-whole-buffer
のようなシンボルには、関数として呼ばれたときにコンピュータが
実行するコードが結び付けられている。このコードを関数定義(function definition)と呼び、
シンボルdefun
(define function(関数を定義する)の略)で始まる
Lispの式を評価することで作成する。 defunは、その引数を通常のようには評価しないので、
スペシャルフォーム(special form)と呼ばれる。
以下の節では、mark-whole-buffer
のようなxyzzyのソースコードの関数定義を調べる。
本節では、関数定義がどのようなものかを理解してもらうために、簡単な関数定義を説明する。
例を簡単にするために、算術演算を使った関数定義を取り上げる。算術演算を使った例が嫌いな人もいるであろうが、
落胆しないでほしい。残りの節で説明するコードには、算術演算や数学はほとんどない。そのほとんどは、
テキストを扱う。
関数定義は、単語defun
に続く最大で5つの部分から成る。
()
を指定する。関数定義の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インタープリタに関数の定義を教える操作である。次節では、インストールについて説明する。
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 コードの恒久的インストールを参照)。
つぎの例を評価すれば、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とタイプする。)
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でコードを書くには、関数を書いてインストールしてテストし、必要に応じて、 修正や機能強化して再インストールする。
関数を対話的にするには、関数の説明文のあとにスペシャルフォームinteractive
で始まるリストを置く。
こうすれば、M-xに続けて関数名をタイプするか、関数に束縛したキー列をタイプすれば
対話的関数を起動できる。たとえば、next-line
を起動するにはC-nとタイプし、
open-line
を起動するにはC-oとタイプする。
対話的関数を対話的に呼び出した場合には、関数が返した値は自動的にはステータスバーに表示されない。 これは、対話的関数を呼び出すのは、単語単位や行単位の移動などの副作用のためであり、 返された値は必要ないからである。キーをタイプするたびに返された値をステータスバーに表示すると、とても煩わしい。
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つある。
上のキー列の例は、どちらもポイントを文字単位に3つ戻る
(multiply-by-seven
にはキーがバインドされていないので、キーバインドを使う例としては使えない)。
前置引数を対話的関数に渡すには、M-3 C-bのようにキーMETAに続けて数をタイプするか、C-u 3 C-bのように C-uに続けて数をタイプする(数をタイプせずにC-uだけをタイプすると、デフォルトは4)。
スペシャルフォーム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'となる。
上の例のmultiply-by-seven
では、interactive
の引数として"p"
を用いた。
この引数は、ユーザーがタイプしたC-uやMETAに続く数を、
関数への引数として渡すコマンドとして解釈するように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つである。
この場合、関数定義はつぎのようになり、buffer
、start
、end
の
各シンボルには、バッファ、リージョンの開始位置、終了位置の現在値をinteractive
が束縛する。
(defun 関数名 (buffer start end) "説明文..." (interactive "BAppend to buffer: \nr") 関数の本体...)
引数がない関数の場合には、interactive
には何も指定しなくてよい。そのような関数では、
単に式(interactive)
を指定する。関数mark-whole-bufferは、このようになっている。
読者のアプリケーションにおいて、あらかじめ定義した文字では不十分な場合には、
interactive
にリストを指定すれば、独自の引数を渡せる。
関数定義を評価して関数定義をインストールすると、xyzzyを終了するまでは関数定義は有効である。 新たにxyzzyを起動したときには、関数定義を再度評価するまでは関数定義はインストールされない。
新たにxyzzyを起動するたびに、自動的にコードをインストールしたくなるであろう。これにはいくつかの方法がある。
load
を用いてxyzzyに各ファイルの関数定義を評価させインストールする。
let
式はLispのスペシャルフォームであり、ほとんどの関数定義で使う必要がある。
let
はシンボルに値を結び付ける、すなわち、束縛するのであるが、
関数の内部と外部で同じ名前の変数を使ってもLispインタープリタが混乱しないような方法でこれを行う。
このスペシャルフォームが必要な理由を理解するために、「家を塗装し直す必要がある」などのように一般的に 「家」と呼ぶような家屋を所有している状況を仮定してみよう。読者が友人宅を訪問したときに、友人が「家」 といったときには、 友人の家を意味しているのであって、読者の家ではないだろう。
友人は彼の家を意味しているのに、読者が読者の家を意味していると考えると、混乱が生じる。
ある関数の内部で使う変数と別の関数の内部で使う変数が同じ名前でも、同じ値を参照する意図がないのであれば、
Lispでも同じことが起こる。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)」の同義語である)。
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...)
つぎの式では、2つの変数zebra
とtiger
を作り、それぞれに初期値を与える。
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つの変数は、zebra
とtiger
である。各変数は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'の位置に表示される。
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を、シンボルpine
とfir
に
nil
を、シンボルoak
にsome
を束縛する。
let
の最初の部分では、変数pine
とfir
は括弧で囲んでない単独のアトムである。
そのため、これらの変数は空リストnil
に束縛される。一方、oak
は、
リスト(oak 'some)
の一部なので、some
に束縛される。同様に、
birch
もリストの一部なので数3に束縛される(数はそれ自身に評価されるので、
数をクオートする必要はない。また、メッセージに数を表示するには`~s'のかわりに`~d'を使う)。
4つの変数をまとめてリストにすることで、let
の本体と区別できるようにする。
defun
やlet
に続く3番目のスペシャルフォームは、条件分岐if
である。
このフォームは、コンピュータに判定を指示する。if
を使わずに関数定義を書くことも可能であろうが、
多くの場面で使用する重要なものなので、ここで説明しておこう。
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!")))
これまでのように関数定義を評価して定義をxyzzyにインストールし、つぎの2つの式を評価して結果を確認できる。
(type-of-animal 'fierce) (type-of-animal 'zebra)
(type-of-animal 'fierce)
を評価すると、ステータスバーにはメッセージIt's a tiger!
が表示される。
(type-of-animal 'zebra)
を評価すると、ステータスバーにはnil
と表示される。
関数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
の最初の使用例では、引数fierce
をtype-of-animal
に渡した。
fierce
はfierce
に等しいので、式(equal characteristic 'fierce)
は真を返す。
すると、if
は第2引数、つまり、真の場合の動作(message "It's a tiger!")
を評価する。
一方、type-of-animal
の2番目の使用例では、引数zebra
をtype-of-animal
に
渡した。zebra
はfierce
に等しくないので、真の場合の動作は評価されず、
if
式はnil
を返す。
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
で調べる値の可能な組み合わせを十分に
考慮し、そのようにプログラムを書く必要がある。)
if
式での判定条件が真かどうかの検査には重要な側面がある。これまで、述語の値としての
「真(true)」と「偽(false)」を、新たなxyzzy Lispオブジェクトであるかのように使ってきた。
実際には、「偽(false)」とは、すでに馴染みのあるnil
のことである。これ以外は、
たとえ何であれ、「真(true)」である。
判定条件の式では、評価結果がnil
以外の値であれば、真(true)と解釈する。いいかえれば、
47などの数、"hello"のような文字列、(nilではない)flowersなどのシンボルやリスト、バッファでさえも、
真と解釈する。
これらの例を示すまえに、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)
関数save-excursion
は、本章で説明する4番目で最後のスペシャルフォームである。
エディタとしてのxyzzy Lispプログラムでは、関数save-excursion
を多用している。この関数は、
ポイントとマークの位置を記録してから、関数の本体を実行し、ポイントやマークの位置が移動していれば
実行前の状態に復元する。この関数の主要な目的は、ポイントやマークの予期しない移動によってユーザーが混乱したり
煩わされないようにすることである。
save-excursion
を説明するまえに、xyzzyのポイントとマークについて復習しておこう。
ポイント(point)とは、カーソルの現在位置である。カーソルがどこにあろうとも、
それがポイントである。端末画面上では、カーソルは文字に重なって表示されるが、ポインタはその文字の直前にある。
xyzzy Lispでは、ポイントは整数である。バッファの最初の文字は0、つぎの文字は1と数える。
関数point
はカーソルの現在位置を数で返す。各バッファごとに、個別のポイントがある。
マーク(mark)も、バッファ内の位置を表す。C-SPC(set-mark-command
)
などのコマンドで、値を設定する。マークを設定してあれば、コマンドC-x C-x
(exchange-point-and-mark
)を用いてカーソルをマークに移動するとともに、カーソル移動前の
ポイント位置にマークを設定する。
バッファのポイントとマークのあいだの部分をリージョン(region)と呼ぶ。
center-region
、kill-region
などのさまざまなコマンドはリージョンに作用する。
スペシャルフォームsave-excursion
は、ポイントとマークの位置を記録し、
Lispインタープリタがスペシャルフォームの本体のコードを評価し終えると、それらの位置を復元する。したがって、
テキストの始めの部分にポイントがあったときに、コードでポイントをバッファの最後に移動したとすると、
save-excursion
は、関数の本体の式を評価し終えるとポイントをもとの場所に戻す。
xyzzyでは、ユーザーが意図しなくても、関数の内部動作の過程でポイントを移動することが多い。たとえば、
indent-region
はポイントを移動する。(ユーザーの視点からは)予期しないような不必要なポイントの
移動でユーザーが混乱しないように、ポイントやマークがユーザーの期待どおりの位置にあるように
save-excursion
を多用する。save-excursion
を使うと、正しく管理できる。
正しく管理できるように、save-excursion
の内側のコードで何か不都合なことが起こった場合
(専門用語でいえば、「異常終了した場合」)でも、save-excursion
はポイントとマークの値を復元する。
この機能はとても役に立つ。
ポイントとマークの値を記録することに加えて、save-excursion
は、カレントバッファも記録しておいて
復元する。つまり、バッファを切り替えるようなコードを書いた場合でも、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 本体...))
これまでの章では、多数の関数やスペシャルフォームを紹介してきた。以下には、 説明しなかった同種の関数も含めて概要を記しておく。
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-lessp
はstring<
とほぼ同じですが、大文字小文字を区別しないところが違います
string=
は一致するかの確認を行います.
string-equal
はstring=
とほぼ同じですが、大文字小文字を区別しません。
その他の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
カレントバッファで取りえるポイントの最大値を返す。ナロイングしていない場合には、バッファの最後である。
fill-column
の現在の値が関数に渡した引数より大きいかどうか調べ、そうならば適切なメッセージを表示するような関数を書いてみよ。[Prev] [Index] [Next] [上端へ移動] [下端へ移動]
このページはMeadow/Emacs memo(http://www.bookshelf.jp/)の
Emacs Lisp プログラミングを改変したものです。
リンクはどのページでも自由にして下さい。
メールはkyupon@gmail.comまで送って下さい。