Lisp/基本/関数
関数 はほとんど全てのプログラミング言語で出会う概念です。しかし Lisp においては特別に重要なものです。歴史的な経緯で言えば Lisp はラムダ計算の、全てのオブジェクトは関数だというところから示唆を受けています。まったく反対側の領域では、関数はほとんどまったくオブジェクトではないとするプログラミング言語もあります。これは Lisp のことではありませんが、ここでの「関数」は他のオブジェクトと同じ特権を持ちます。このことについてはこの章で論じます。
関数定義
[編集]関数はほとんどの場合 defun(DEfine FUNction)マクロを用いて作成されます。このマクロは引数の list と関数の body と呼ばれる連続した Lisp式 を持ちます。 defun の典型的な使用法は以下の様になります。
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(+ x1 x2))
ここでは引数の list は (x1 x2) となり、 body は (print x1) (print x2) (+ x1 x2) となります。関数が呼ばれたとき、 body のそれぞれの式は最初から最後にかけて順番に評価されます。最後の式にたどり着く前に、何かがあって関数から返り値が欲しい、ということになったときはどうなるでしょうか? return-form マクロによって返り値を得ることができます。例えば以下の様にです。
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(unless (and (numberp x1) (numberp x2))
(return-from print-arguments-and-return-sum "Error!"))
(+ x1 x2))
return-from の二番目の引数はoptional(任意)です。これはこのマクロを呼び出す際は引数が一つだけでも呼び出すことが出来るということです。上の例では関数は nil を返します。しかし、どのようにしてこのような結果になるのでしょうか?今回の関数では引数を2つ受け取ります。それ以上でもなく、それ以下でもありません。答えは、我々が "引数の list" と呼んでいるものは見た目どおりの単純なものではないということです。実はこれは、 lambda list と呼ばれるもので出くわすことになるのは今回だけではありません。今後、どのように optional(任意) の引数を可能にするのか、keyword 引数を可能にするのかを見ていきます。
データとしての関数
[編集]初心者向けチュートリアルの章で見たように、 Lisp の関数は他の別のオブジェクトのように扱うことが可能です。例えば、関数は変数に保存することも出来ますし、他の関数の引数として渡すことも、関数の返り値になることもできます。前の章で関数を定義しましたが、前の章で定義した関数は、シンボル print-arguments-and-return-sum の function cell に保存されています。 defun が関数をそこに配置したのです。しかし、この関数が永遠にこの場所に束縛されるわけではありません。この関数を抜き出して、また別のシンボルに配置することも出来るのです。例えば、シンボルの function cell を利用するには symbol-function を利用します。では他のシンボルに先ほどの関数を保存してみましょう。
(setf paars (symbol-function 'print-arguments-and-return-sum))
早速打ち込んでみての最初の反応はおよそ以下のようなものでしょう。
>(paars 1 1)
EVAL: undefined function PAARS
[Condition of type SYSTEM::SIMPLE-UNDEFINED-FUNCTION]
こうなってしまうのはシンボルの function cell ではなく value cell に関数を配置したからです。修復するのは簡単です。
(setf (symbol-function 'paars)
(symbol-function 'print-arguments-and-return-sum))
symbol-function を利用しているので setf を使用することができます。 (paars 1 1)
これで目的のものが得られました。
symbol-function が存在するのには理由があるのですが、実際のコードではほとんど使われることはありません。なぜなら他の Lisp の機能に取って代わられたからです。そのうちの一つが特殊命令 function です。これは特殊命令 symbol-function のように動きますが、引数を評価しません。その代わりに、そのシンボルに束縛されている関数を返します。それは実質的な function cell 出はないことに注意です。 (function foo)
は #'
に短縮することもでき、非常に有用です。一方で以下の様には書くことは出来ません。
(setf (function paars) (function print-arguments-and-return-sum))
幸いなことにシンボルの function cell 以外の場所から関数を呼び出すことは可能です。 function designator はシンボル(このケースでは function cell が使われています)か関数そのもののどちらでもあります。 funcall や apply は function designator を使用して関数を呼び出すのに使います。シンボル paars が今は同じ関数をその function cell と value cell に含んでいることを思い出してください。 value cell を変更してみると違いがわかります。
(setf paars #'+)
それでは funcall をそれぞれ違った方法で試してみましょう。
(funcall paars 1 2) ;equivalent to (+ 1 2)
(funcall 'paars 1 2) ;equivalent to (funcall (symbol-function paars) 1 2)
(funcall #'paars 1 2) ;equivalent to (paars 1 2)
二番目と三番目の例の違いは paars が flet や labels を用いて他の関数に一時的に束縛されたものかどうかということです。三番目の funcall はこの一時的な関数を使い、一方で二番目の funcall は自身の function cell を使っているのです。