[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Lispプログラムは、主にLisp関数から構成されます。 本章では、関数とはなにか、引数をどのように受け取るのか、 どのように関数を定義するのかを説明します。
11.1 関数とはなにか | Lisp functions vs. primitives; terminology. | |
11.2 ラムダ式 | How functions are expressed as Lisp objects. | |
11.3 関数を命名する | A symbol can serve as the name of a function. | |
11.4 関数を定義する | Lisp expressions for defining functions. | |
11.5 関数呼び出し | How to use an existing function. | |
11.6 マップ関数 | Applying a function to each element of a list, etc. | |
11.7 無名関数 | Lambda expressions are functions with no names. | |
11.8 関数セルの内容の参照 | Accessing or setting the function definition of a symbol. | |
11.9 インライン関数 | Defining functions that the compiler will open code. | |
11.10 関数に関連したその他の話題 | Cross-references to specific Lisp primitives that have a special bearing on how functions work. |
一般的には、関数とは、引数(arguments)と呼ばれる値を与えられ、 計算を行うための規則です。 この計算結果を関数の値と呼びます。 計算では副作用、つまり、変数の値やデータ構造の内容に継続する変更 を伴うこともできます。
Emacs Lispの関数や関数のようなオブジェクトに関する重要な用語をあげておきます。
car
やappend
などのCで書いた
Lispから呼び出し可能な関数である。
これらの関数は、組み込み関数とかsubrsとも呼ぶ。
(スペシャルフォームは基本関数とも考えられる。)
関数を基本関数として実装する理由は、 それが基本的なものである、 それがオペレーティングシステムの機能に対する 低レベルのインターフェイスを提供する、 あるいは、高速に動作する必要があるからである。 基本関数を変更したり追加する唯一の方法は、 Cソースを変更してエディタを再コンパイルすることである。 see section B.5 Emacs基本関数の書き方。
command-execute
が起動できるオブジェクトであり、
キー列に対して定義できる。
いくつかの関数はコマンドである。
Lispで書いた関数に対話宣言(see section 20.2 コマンドの定義)が含まれているとき、
その関数はコマンドである。
そのような関数は、他の関数と同様にLisp式から呼び出すことができる。
その場合、関数がコマンドであるという事実は関係ない。
キーボードマクロ(文字列かベクトル)もコマンドであるが、 それらは関数ではない。 シンボルの関数定義がコマンドであれば、シンボルはコマンドである。 そのようなシンボルは、M-xで起動できる。 シンボルの定義が関数であれば、シンボルは関数でもある。
t
を返す。
t
を返す。
(subrp 'message) ; |
t
を返す。
たとえば、つぎのとおり。
(byte-code-function-p (symbol-function 'next-line)) => t |
Lispで書いた関数はつぎのようなリストです。
(lambda (arg-variables...) [documentation-string] [interactive-declaration] body-forms...) |
このようなリストをラムダ式(lambda expression)と呼びます。 Emacs Lispでは、これは式として正しいもので、 それ自身に評価されます。 Lispの他の方言では、ラムダ式は正しい式ではありません。 いずれの場合でも、その主な用途は式として評価することではなく、 関数として呼び出すことです。
11.2.1 ラムダ式の構成要素 | The parts of a lambda expression. | |
11.2.2 簡単なラムダ式の例 | A simple example. | |
11.2.3 引数リストのその他の機能 | Details and special features of argument lists. | |
11.2.4 関数の説明文字列 | How to put documentation in a function. |
Lispで書いた関数(『ラムダ式』)はつぎのようなリストです。
(lambda (arg-variables...) [documentation-string] [interactive-declaration] body-forms...) |
ラムダ式の先頭要素は、つねにシンボルlambda
です。
このシンボルは、リストが関数を表すことを示します。
関数はlambda
で始まると定義してあるのは、
他の目的向けの他のリストが誤って正しい関数とならないようにするためです。
第2要素は、シンボルのリスト、つまり、引数変数名です。 これをラムダリスト(lambda list)と呼びます。 Lisp関数が呼ばれると、引数値をラムダリストの変数に対応させ、 指定した値を持つローカル束縛になります。 See section 10.3 ローカル変数。
説明文字列は、関数定義の内側にあるLisp文字列オブジェクトであり、 Emacsのヘルプ機能に対して関数を記述します。 See section 11.2.4 関数の説明文字列。
対話宣言は、(interactive code-string)
の形式のリストです。
この宣言は、関数が対話的に使われたときに、
どのように引数を与えるかを指定します。
この宣言を有する関数をコマンド(commands)と呼びます。
コマンドは、M-xで呼び出したり、キーにバインドできます。
このように呼ばれることを意図していない関数には、
対話宣言を付けてはいけません。
対話宣言の書き方については、See section 20.2 コマンドの定義。
残りの要素は、関数の本体(body)です。 関数の動作を行うLispコードです (Lispプログラマとしては、『評価するべきLispフォームのリスト』という)。 関数が返す値は、本体の最後の要素が返す値です。
つぎの関数を考えてみましょう。
(lambda (a b c) (+ a b c)) |
この関数を呼び出すには、つぎのように式のCARにこの関数を書きます。
((lambda (a b c) (+ a b c)) 1 2 3) |
この呼び出しは、変数a
には1、変数b
には2、
変数c
には3を束縛し、ラムダ式の本体を評価します。
本体の評価ではこれらを加算し、結果6を生じます。
したがって、この関数呼び出しは6を返します。
つぎの例のように、他の関数呼び出しの結果が引数になることもあります。
((lambda (a b c) (+ a b c)) 1 (* 2 3) (- 5 4)) |
これは、引数、1
、(* 2 3)
、(- 5 4)
を
左から右へ順に評価します。
そして、引数値、1、6、1にラムダ式を適用し、値8を生じます。
このようにフォームのCARとしてラムダ式を書くのは、
あまり便利ではありません。
スペシャルフォームlet
(see section 10.3 ローカル変数)を使って、
ローカル変数を作ってそれらに値を与えても、同じ結果を得られます。
さらに、let
は見通しがよく使いやすいです。
実用上、ラムダ式は、シンボルの関数定義として格納して名前付き関数を作るか、
他の関数に引数として渡します(see section 11.7 無名関数)。
しかしながら、スペシャルフォームlet
がなかった初期のLispでは、
ラムダ式を明示的に呼び出すことはとても便利でした。
その頃では、ラムダ式はローカル変数を束縛し初期化する唯一の方法でした。
単純な関数の例(lambda (a b c) (+ a b c))
では、
3つの引数変数を指定しているので、これは3引数で呼び出す必要があります。
2引数や4引数で呼び出そうとすると、
エラーwrong-number-of-arguments
になります。
特定の引数を省略できる関数を書けると便利なことがしばしばあります。
たとえば、関数substring
は3つの引数、つまり、
文字列、開始と終了の添字を取りますが、
第3引数を省略するとデフォルトは文字列のlengthになります。
list
や+
のように、
特定の関数では任意個数の引数を受け付けると便利なこともあります。
関数呼び出し時に省略してもよい引数を指定するには、
省略可能な引数のまえにキーワード&optional
を含めるだけです。
0個以上の引数のリストを指定するには、
最後の引数のまえにキーワード&rest
を含めます。
したがって、引数リストの完全な構文はつぎのようになります。
(required-vars... ; 必須の引数 [&optional optional-vars...] ; 省略可能な引数 [&rest rest-var]) ; 残りの引数 |
角括弧は、&optional
や&rest
の節や
それに続く変数は省略できることを示します。
関数呼び出し時には、各required-varsに1つの実引数が必要です。
0個以上のoptional-varsにも実引数が必要ですが、
ラムダリストに&rest
がない限り、
optional-varsの個数を超える実引数は指定できません。
&rest
があれば、任意個の余分な実引数を指定できます。
&optional
や&rest
に対応する実引数を省略すると、
それらのデフォルトはnil
です。
関数では、nil
を明示した引数と省略した引数とを区別する方法はありません。
しかしながら、関数本体でnil
を適切な意味ある値の省略と
みなすことは自由です。
substring
はそのようにしています。
substring
の第3引数がnil
であると、
指定した文字列の長さを使うことを意味します。
Common Lispに関した注意:Common Lispでは、省略可能引数を省略したときのデフォルト値を関数で指定できる。 Emacs Lispではつねに
nil
を使う。 Emacs Lispには、明示的に引数を指定したかどうか調べる 『supplied-p』変数はない。
たとえば、引数リストはつぎのようになります。
(a b &optional c d &rest e) |
これは、a
とb
に最初の2つの実引数を束縛し、これらは必須です。
さらに1個か2個の引数を指定すると、
それらは、それぞれc
とd
に束縛します。
最初の4個よりあとの引数はリストにまとめ、
e
にそのリストを束縛します。
引数が2個だけであると、c
はnil
です。
引数が2個か3個だけであると、d
はnil
です。
引数が4個以下であると、e
はnil
です。
省略可能な引数のあとに必須引数を指定する方法はありませんし、
それには意味がありません。
なぜそうなのかを理解するために、上の例で、
c
は省略可能であり、d
は必須であるとしましょう。
3つの実引数を指定したとき、どの引数を3番目と考えるのでしょう?
同様に、&rest
のうしろに余分に(必須、もしくは省略可能な)引数が
あっても意味がありません。
引数リストと正しい呼び出しの例をあげます。
((lambda (n) (1+ n)) ; 1個が必須 1) ; 引数は1個だけ => 2 ((lambda (n &optional n1) ; 1個は必須、1個は省略可 (if n1 (+ n n1) (1+ n))) ; 引数は1個か2個 1 2) => 3 ((lambda (n &rest ns) ; 1個は必須、あとは残り全部 (+ n (apply '+ ns))) ; 引数は1個以上いくつでもよい 1 2 3 4 5) => 15 |
ラムダ式には、ラムダリストの直後に 説明文字列(documentation string)があってもかまいません。 この文字列は関数の実行には影響しません。 コメントのようなものですが、Lisp内部に現れる系統的なコメントであり、 Emacsのヘルプ機能が使用します。 documentation-stringの参照方法については、See section 23. 説明文。
読者のプログラムの関数すべてに、 たとえ内部的に使用されるものであっても説明文字列を与えることはよいことです。 説明文字列はコメントに似ていますが、参照するのはもっと簡単です。
説明文字列の先頭行は、その1行で完結しているべきです。
というのは、apropos
は先頭行だけを表示するからです。
関数の機能をまとめた1つか2つの文にしましょう。
説明文字列の先頭は、ソースファイル上では普通字下げしてあるでしょうが、 それらの空白は文字列を始めるダブルクォートのまえにありますから、 それらは文字列の一部ではありません。 説明文字列の残りの行を字下げして、 プログラムソース上でテキスト行が揃うようにする人もいます。 しかし、それはまちがいです。 後続の行の字下げは文字列の内側にあります。 ソースファイルで綺麗に見えても、 ヘルプコマンドの表示では不恰好になります。
関数の必須の構成要素(本体)があとに続くのに、 説明文字列を省略できるのを不思議に思うかもしれません。 文字列を評価すると、副作用なしに、その文字列を返すので、 それが本体の最後のフォームでなければ、なんの効果もありません。 したがって、実用上、本体の最初のフォームと 説明文字列を混同することはありません。 本体のフォームが文字列だけであると、 それは戻り値でもあり説明文字列でもあります。
ほとんどの計算機言語では、各関数には名前があります。
名前のない関数という考えは本質的ではありません。
Lispでは、もっとも厳密にいえば、関数には名前はありません。
関数は、先頭要素が単にlambda
であるリスト、
バイトコード関数オブジェクト、あるいは、基本関数のsubrオブジェクトです。
しかしながら、シンボルは関数の名前として働きます。 シンボルの関数セル(function cell、see section 7.1 シンボルの構成要素)に 関数を入れると、このようになります。 そうすると、シンボルそのものは正当な呼び出し可能な関数となり、 関数セルが参照するリストやsubrオブジェクトと等価になります。 関数セルの内容をシンボルの関数定義(function definition)とも呼びます。 シンボルのかわりにシンボルの関数定義を使う処理を シンボルの関数間接(symbol function indirection)と呼びます。 See section 8.2.4 シンボルの関数間接。
実用上、ほとんどすべての関数には、このようにして名前が付いていて、
その名前で参照します。
たとえば、シンボルcar
は、
その関数セルに基本関数のsubrオブジェクト#<subr car>
が格納してあるので、
その動作を行う関数として動作します。
関数に名前を与えるのは、Lisp式からその名前で参照できると便利だからです。
#<subr car>
のような基本関数のsubrオブジェクトでは、
名前はそれらを参照する唯一の方法です。
そのようなオブジェクトには入力構文はありません。
Lispで書いた関数では、明示的なラムダ式より名前を使うほうがより便利です。
また、関数に名前があればそれを参照できます。
つまり、再帰呼び出しができます。
関数の名前をその定義そのものに書くことは、
関数定義がそれ自身を指すようにする
(これは不可能ではないにしても、実用上はさまざまな欠点がある)よりは、
とても便利です。
関数を指名するシンボルで関数をしばしば識別します。
たとえば、しばしば『関数car
』といって、
シンボルcar
と関数定義である基本関数のsubrオブジェクトとを区別しません。
ほとんどの目的には、区別する必要はありません。
たとえそうであっても、関数に一意な名前は必要ないことを
心に留めておいてください。
関数オブジェクトは普通1つのシンボルの関数セルだけに現れますが、
これは単なる便法です。
fset
を使って、複数のシンボルに格納するのは簡単です。
そうすると、各シンボルは同じ関数を同等に指名します。
関数名として使うシンボルは、変数としても使えます。 シンボルのこれら2つの使い方は独立していて衝突しません。 (SchemeなどのLispの方言のなかには、 シンボルの値とその関数定義を区別しないものもある。 変数としてのシンボルの値は、その関数定義でもある。) シンボルに関数定義を与えていないと、そのシンボルを関数としては使えません。 これは、シンボルに変数としての値があるかどうかには関係しません。
関数を作成するときには、普通、関数に名前を与えます。
これを関数を定義すると呼び、
スペシャルフォームdefun
で行います。
defun
は、新たにLisp関数を定義する普通の方法である。
これは、シンボルnameをつぎのような関数として定義する。
(lambda argument-list . body-forms) |
defun
は、このラムダ式をnameの関数セルに格納する。
値nameを返すが、普通、これは無視する。
前述(see section 11.2 ラムダ式)のように、
argument-listは引数名のリストであり、
キーワード&optional
や&rest
が入っていてもよい。
また、body-formsの最初の2つは、説明文字列と対話宣言でもよい。
同一のシンボルnameを変数として使っていても衝突はない。 というのは、シンボルの値セルは関数セルとは独立だからである。 see section 7.1 シンボルの構成要素。
例を示そう。
(defun foo () 5) => foo (foo) => 5 (defun bar (a &optional b &rest c) (list a b c)) => bar (bar 1 2 3 4 5) => (1 2 (3 4 5)) (bar 1) => (1 nil nil) (bar) error--> Wrong number of arguments. (defun capitalize-backwards () "Upcase the last letter of a word." (interactive) (backward-word 1) (forward-word 1) (backward-char 1) (capitalize-word 1)) => capitalize-backwards |
既存の関数を意図せずに再定義しないように注意すること。
defun
は、たとえcar
などの基本関数であっても、
なんの躊躇も注意もせずに再定義してしまう。
既存関数の再定義は注意深く行うが、
不本意な再定義と熟考した再定義を区別する方法はない。
defalias
を使う正しい場所は、
特定の関数名が定義されている場所である。
特に、ロード中のソースファイルで明示的に名前が現れている場所である。
というのは、defalias
は、defun
と同様に、
関数が定義されたファイルを記録するからである(see section 14.7 アンロード)。
一方、他の目的で関数定義を操作するプログラムでは、
そのような記録を保持しないfset
を使うのがよい。
defun
のように関数を定義し、かつ、
Lispコンパイラに関数定義を展開するように指示する
defsubst
も参照してください。
See section 11.9 インライン関数。
関数を定義することは、全体の半分でしかありません。 関数を呼ぶまでは、つまり、実行を命じなければ、関数はなにもしません。 関数呼び出しは起動(invocation)ともいいます。
関数を起動するもっとも一般的な方法は、リストを評価することです。
たとえば、リスト(concat "a" "b")
を評価すると、
関数concat
を引数"a"
と"b"
で呼び出します。
評価についてはSee section 8. 評価。
読者のプログラムで式としてリストを書くときには、
呼び出す関数名を読者のプログラムに書きます。
つまり、プログラムを書くときに、
どの関数をどれだけの引数で呼び出すかを指定できることを意味します。
これが、普通にしたいことでしょう。
呼び出す関数を実行時に計算する必要がある場合もあるでしょう。
それには、関数funcall
を使います。
渡す引数の個数を実行時に決定する必要があるときには、
apply
を使います。
funcall
は、functionをargumentsで呼び出し、
functionがなにを返そうともそれを返す。
funcall
は関数なので、functionの呼び出しを評価するまえに
functionを含めた引数すべてを評価する。
つまり、呼び出す関数を得るためのどんな式でも使えることを意味する。
また、funcall
は、読者がargumentsに書いた式を見ることはなく、
それらの値だけを見ることになる。
これらの値は、functionを呼び出す操作において、
2回目の評価を行うことはない。
funcall
は、通常の関数呼び出し処理において、
引数を評価し終えたところから始める。
引数functionは、Lisp関数か基本関数である必要がある。
スペシャルフォームやマクロは許されない。
それらには、『未評価』の引数式を与えたときだけ意味があるからである。
funcall
ではそのようにできない。
なぜなら、上の説明でわかるように、
未評価の引数をまったく知らないからである。
(setq f 'list) => list (funcall f 'x 'y 'z) => (x y z) (funcall f 'x 'y '(z)) => (x y (z)) (funcall 'and t nil) error--> Invalid function: #<subr and> |
これらの例をapply
の例と比較してほしい。
apply
は、funcall
のように、
functionをargumentsで呼び出すが、1点だけ異なる。
argumentsの最後はオブジェクトのリストであり、
functionにはこれを、単一のリストではなく、個々の引数として渡す。
これを、apply
は、
このリストの個々の要素が引数となるように分配するという。
apply
は、functionの呼び出し結果を返す。
funcall
と同様に、functionはLisp関数か基本関数である必要がある。
スペシャルフォームやマクロは、apply
では意味がない。
(setq f 'list) => list (apply f 'x 'y 'z) error--> Wrong type argument: listp, z (apply '+ 1 2 '(3 4)) => 10 (apply '+ '(1 2 3 4)) => 10 (apply 'append '((a b c) nil (x y z) nil)) => (a b c x y z) |
apply
を使った興味深い例として、
11.6 マップ関数のmapcar
の説明を見てほしい。
Lisp関数にとっては、引数として関数を受け取ったり、
データ構造(特に、フック変数や属性リスト)内の関数を探して
funcall
やapply
を使ってそれを呼び出すことは一般的です。
関数引数を受け付ける関数を
しばしばファンクショナル(functionals)と呼びます。
場合によっては、ファンクショナルを呼び出すときには、 引数としてなにもしない関数(no-op)を指定できると有用です。 つぎのものは、2種類のなにもしない関数です。
nil
を返す。
マップ関数(mapping function)は、
リストや他の集まりの各要素に指定した関数を適用します。
Emacs Lispにはそのような関数がいくつかあります。
mapcar
とmapconcat
はリストを走査するもので、ここで説明します。
オブジェクト配列obarray内のシンボルについて
マップする関数mapatoms
については、
See section 7.3 シンボルの作成とインターン。
これらのマップ関数では、文字テーブルは扱えません。
というのは、文字テーブルは疎な配列であり、その添字範囲も非常に大きいからです。
文字テーブルの疎な性質を考慮して文字テーブルについてマップするには、
関数map-char-table
(see section 6.6 文字テーブル)を使います。
mapcar
は、sequenceの各要素に順にfunctionを適用し、
結果のリストを返す。
引数sequenceは文字テーブル以外の任意の種類のシーケンスでよい。 つまり、リスト、ベクトル、ブールベクトル、あるいは、文字列である。 結果はつねにリストである。 結果の長さはsequenceの長さと同じである。
たとえば、つぎのとおり。
(mapcar 'car '((a b) (c d) (e f)))
=> (a c e)
(mapcar '1+ [1 2 3])
=> (2 3 4)
(mapcar 'char-to-string "abc")
=> ("a" "b" "c")
;; |
mapconcat
は、sequenceの各要素にfunctionを適用する。
それらの結果は、文字列である必要があり、連結される。
mapconcat
は、結果の文字列のあいだに文字列separatorを挿入する。
普通、separatorは、空白やコンマ、その他の句読点を含む。
引数functionは、引数を1つ取る関数であり、 文字列を返す必要がある。 引数sequenceは、文字テーブル以外の任意の種類のシーケンスでよい。 つまり、リスト、ベクトル、ブールベクトル、あるいは、文字列である。
(mapconcat 'symbol-name '(The cat in the hat) " ") => "The cat in the hat" (mapconcat (function (lambda (x) (format "%c" (1+ x)))) "HAL-8000" "") => "IBM.9111" |
Lispでは、関数とは、lambda
で始まるリスト、
そのようなリストをコンパイルしたバイトコード関数、
あるいは、基本関数のsubrオブジェクトです。
名前は『余分』なのです。
普通の関数はdefun
で定義し、そのとき名前を与えますが、
明示的なラムダ式、つまり、無名関数を使ったほうがより簡素な場合もあります。
そのようなリストは、関数名を使える場面ならば、どこでも使えます。
そのようなリストをどんな方法で作っても、正しい関数となります。 つぎのようにしてもかまわないのです。
(setq silly (append '(lambda (x)) (list (list '+ (* 3 4) 'x)))) => (lambda (x) (+ 12 x)) |
これは、(lambda (x) (+ 12 x))
のようなリストを計算し、
その値をsilly
の値(関数定義ではない!)とします。
この関数はつぎのように呼び出せます。
(funcall silly 1) => 13 |
((silly 1)
と書いても動作しない。
なぜなら、この関数は、silly
の関数定義ではないからである。
silly
には関数定義を与えてなく、
変数としての値を与えただけである。)
ほとんどの場合、無名関数は読者のプログラムに現れる定数です。
たとえば、関数mapcar
の引数の1つに渡したいときなどです。
mapcar
は、リストの各要素に指定した関数を適用します。
第3引数に関数を取る関数change-property
を定義します。
(defun change-property (symbol prop function) (let ((value (get symbol prop))) (put symbol prop (funcall function value)))) |
ここで、数を2倍する関数を渡してchange-property
を使う
関数を定義します。
(defun double-property (symbol prop) (change-property symbol prop '(lambda (x) (* 2 x)))) |
このような場合、つぎのように、無名関数をクォートするには、
単純なクォートのかわりにスペシャルフォームfunction
を使います。
(defun double-property (symbol prop) (change-property symbol prop (function (lambda (x) (* 2 x))))) |
quote
のかわりにfunction
を使った場合に違いがでるのは、
関数double-property
をコンパイルしたときです。
たとえば、double-property
の2番目の定義をコンパイルすると、
無名関数もコンパイルされます。
一方、普通のquote
を使った最初の定義をコンパイルすると、
change-property
へ渡す引数は、書いたとおりのリストです。
(lambda (x) (* x 2)) |
Lispコンパイラは、このリストが関数に見えたとしても、
このリストを関数とはみなしません。
というのは、コンパイラにはchange-property
がリストになにを行うか
わからないからです。
たぶん、第3要素のCARがシンボル*
か
どうか調べればよいのでしょう!
function
を使うと、コンパイラに対して先へ進んで
定数の関数をコンパイルしても安全であることを伝えます。
関数名をクォートするときにquote
のかわりにfunction
を
書くこともありますが、この用法はコメントのようなものです。
(function symbol) == (quote symbol) == 'symbol |
入力構文#'
は、function
の省略形です。
たとえば、
#'(lambda (x) (* x x)) |
は、つぎと等価です。
(function (lambda (x) (* x x))) |
quote
に等価である。
しかし、これは、Emacs Lispコンパイラに対しては注意書きとして働き、
function-objectを関数としてのみ使う意図があり、
したがって、コンパイルしても安全であることを意味する。
8.3 クォートのquote
と比較してほしい。
function
と無名関数を用いた実際的な例は、
23.2 説明文字列の参照のdocumentation
を参照してください。
シンボルの関数定義(function definition)とは、 シンボルの関数セルに格納されたオブジェクトです。 ここで説明する関数は、シンボルの関数セルを参照したり、調べたり、 設定したりします。
8.2.4 シンボルの関数間接の関数indirect-function
も参照してください。
void-function
を通知する。
この関数は、返すオブジェクトが正しい関数であるかどうか検査しない。
(defun bar (n) (+ n 2)) => bar (symbol-function 'bar) => (lambda (n) (+ n 2)) (fset 'baz 'bar) => bar (symbol-function 'baz) => bar |
シンボルに一度も関数定義を与えていないと、
そのシンボルの関数セルは空(void)であるといいます。
いいかえれば、関数セルにはどんなLispオブジェクトも入っていません。
そのようなシンボルを関数として呼び出そうとすると、
エラーvoid-function
を通知します。
空(void)は、nil
やシンボルvoid
と違うことに注意してください。
シンボルnil
もvoid
もLispオブジェクトであり、
それらは他のオブジェクトと同様に関数セルに格納できます
(そして、それらをdefun
で定義しておけば、正しい関数である)。
空の関数セルには、どんなオブジェクトも含まれていません。
シンボルの関数定義が空かどうかはfboundp
で調べることができます。
シンボルに関数定義を与えたあとでも、
fmakunbound
を使ってふたたび空にできます。
t
を返し、
さもなければnil
を返す。
オブジェクトが正しい関数であるかどうか検査しない。
void-function
を引き起こす。
(10.4 変数が『空』であるときのmakunbound
も参照)。
(defun foo (x) x) => foo (foo 1) =>1 (fmakunbound 'foo) => foo (foo 1) error--> Symbol's function definition is void: foo |
この関数の普通の3つの使い方はつぎのとおり。
fset
のかわりにdefalias
を使うべきである。
see section 11.4 関数を定義する。)
defun
ではできない。
たとえば、fset
を使って、s1
に関数定義として
別のシンボルs2
を与えることができる。
すると、s1
は、s2
の現在の定義の別名として働く。
(これをs1
の定義と考えるのであれば、
やはり、fset
のかわりにdefalias
を使う。)
defun
が基本関数でなかったならば、
fset
を使って(マクロとして)Lispでdefun
を書くことができる。
これらの使用例を示す。
;; |
既存の関数定義を拡張する関数を書くときには、 つぎのような常套句を使うこともあります。
(fset 'old-foo (symbol-function 'foo)) (defun foo () "Just like old-foo, except more so." (old-foo) (more-so)) |
foo
が自動ロードと定義されていると、これは正しく動作しません。
そのような場合には、foo
がold-foo
を呼び出すと、
Lispはファイルをロードしてold-foo
を定義しようとします。
しかし、これはold-foo
ではなくfoo
を定義するので、
正しい結果を得られません。
この問題を回避する唯一の方法は、
foo
の古い定義を移すまえに、確実にファイルをロードしておくことです。
しかし、別の箇所で定義された関数を再定義するLispファイルに対しては、 いずれにしても、これではモジュール化も見通しもよくありません。 アドバイズ機能(see section 16. Emacs Lisp関数のアドバイス)を使えば、見通しがよくなります。
defun
のかわりにdefsubst
を使うことで、
インライン関数(inline function)を定義できます。
インライン関数は、1つの点を除いて、普通の関数と同様に動作します。
そのような関数の呼び出しをコンパイルすると、
関数定義は呼び出し側で展開されます。
関数を展開すると明示的な呼び出しが高速になります。 しかし、それには欠点もあります。 その1つは、柔軟性を減らすことです。 関数の定義を変更しても、コンパイルし直すまでは、 すでに展開された呼び出しは古い定義を使い続けます。 関数を再定義できる柔軟性はEmacsでは重要な機能ですから、 速度が本当に重要でなければ、関数を展開すべきではありません。
別の欠点は、大きな関数を展開すると、コンパイルした関数のサイズが ファイル内でもメモリ上でも増加します。 インライン関数のスピードの利点は、小さな関数でもっとも大きいので、 一般には大きな関数を展開すべきではありません。
インライン関数が実行するのと同じコードに展開するようにマクロを定義する
ことも可能です。
(see section 12. マクロ。)
しかし、マクロは式で直接使った場合に制限されます。
マクロは、apply
やmapcar
などで呼び出せません。
さらに、普通の関数をマクロに変換するには、多少の作業が必要です。
普通の関数をインライン関数に変換するのはとても簡単です。
単に、defun
をdefsubst
で置き換えるだけです。
インライン関数の各引数は、ちょうど1回だけ評価されるので、
マクロのように本体で引数を何回使うかを考慮する必要はありません。
(see section 12.6.1 マクロ引数の複数回評価。)
インライン関数は、マクロと同様に、 同じファイル内の定義位置よりうしろで使われ展開されます。
関数呼び出しと関数定義に関連したいくつかの関数の一覧をあげておきます。 これらは別の場所で説明してありますが、相互参照をあげておきます。
apply
autoload
call-interactively
commandp
documentation
eval
funcall
function
ignore
indirect-function
interactive
interactive
の使い方。
interactive-p
mapatoms
mapcar
map-char-table
mapconcat
undefined
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |