プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(14)

今日も「実践Common Lisp」です。
6章の変数までやったら「Land of Lisp」に戻ろうかなと予定中です。
次は5章、関数の話です。

関数定義

関数はDEFUNを使って定義します。これは知っております。
基本的な枠組みは下記のようなものです。

(defun name (parameter)
 "省略可能なドキュメンテーション文字列"
 body-form)

…一つ知らないものがありますね。
とりあえず一つずつ説明を読みます。
nameは関数の名前ですね。どの様なシンボルでも名前とできます。
注釈で、「まあ、ほとんど使える」と濁されてはいますが。
parameterは関数に渡す引数を保持するのに使う変数を定義します。
次は、なんか文字列ですね。関数の目的を説明するドキュメントにできるらしいです。
やってみます。

CL-USER> (defun hello-world ()
	   "ハローワールドする"
	   (format t "hello, world"))
HELLO-WORLD
CL-USER> (documentation 'hello-world 'function)
"ハローワールドする"
CL-USER>

ほう!できました。大人数で開発する時に役に立ちそうですね。
でも省略可能だし、処理系によっては設定が必要だったり
するそうなので、基本的には使わないと思います。

body-formは任意の個数のLisp式が構成します。
定義した関数が呼び出されたときに順番に評価され、
最後の式の値が関数の値として返されます。

だいたい知っている知識でした。流石に当たり前ですが。
でも、ここから面白くなりそうです。

関数のパラメータリスト

パラメータリストの基本的な役割は、先程説明したとおりです。
パラメータリストが単純な変数の名前からなるリストのとき、
そのパラメータを必須パラメータと言うそうです。
関数が呼び出されたとき、必須パラメータにはそれぞれ対応する
変数が与えられなければなりません。
しかし、もっと柔軟に関数呼び出し時の引数と
呼び出される関数のパラメータを対応付ける方法もあるそうです。

オプショナルパラメータ

多くの関数は必須パラメータだけで十分ですが、
全ての関数がそう単純というわけではないそうです。
要は、細かい設定をしたい人だけが設定するパラメータを持つ関数を、
別に細かいところはデフォルトの値でいい人も使えるようにしたい
場合があったりするよねって話ですね。
オプショナルパラメータというのを設定すればそれができるようです。

オプショナルパラメータ付きの関数を設定するのは簡単みたいです。

CL-USER> (defun hoge (a b &optional c d) (list a b c d))
HOGE
CL-USER> (hoge 'a 'b)
(A B NIL NIL)
CL-USER> (hoge 'a 'b 'c)
(A B C NIL)
CL-USER> (hoge 'a 'b 'c 'd)
(A B C D)
CL-USER> (hoge 'a 'b 'c 'd 'e)
; Evaluation aborted on #<SB-INT:SIMPLE-PROGRAM-ERROR "invalid number of arguments: ~S" {100495D463}>.
CL-USER> (hoge 'a)
; Evaluation aborted on #<SB-INT:SIMPLE-PROGRAM-ERROR "invalid number of arguments: ~S" {1004AF76B3}>.
CL-USER> 

必須パラメータを設定したあと、&optionalというシンボルを置き、
その後にオプショナルパラメータを設定します。
引数が必須パラメータの数に満たない場合や、
必須パラメータ+オプショナルの数を超えた場合はエラーです。
まあ、当然ですね。
オプショナルパラメータに引数が割り当てられなかった場合は
NILが設定されます。

デフォルト値をNIL以外にする方法も勿論あるようです。

CL-USER> (defun hoge (a &optional (b 2)) (* a b))
WARNING: redefining COMMON-LISP-USER::HOGE in DEFUN
HOGE
CL-USER> (hoge 8)
16
CL-USER> (hoge 8 7)
56
CL-USER>

デフォルト値を設定する時は、単に変数名を指定する代わりに
変数名とデフォルト値からなるリストを指定すれば良いようです。
このデフォルト値としては単純に値を規定するのが一般的だそうです。

このデフォルト値には他のパラメータを設定することもできるらしいです。

CL-USER> (defun hoge (a &optional (b a)) (* a b))
WARNING: redefining COMMON-LISP-USER::HOGE in DEFUN
HOGE
CL-USER> (hoge 6)
36
CL-USER> (hoge 2)
4
CL-USER> (defun hoge (&optional (a 0) (b a)) (* a b))
WARNING: redefining COMMON-LISP-USER::HOGE in DEFUN
HOGE
CL-USER> (hoge 5)
25
CL-USER> (hoge)
0
CL-USER> (hoge 7 8)
56
CL-USER>

凄いわかりやすい機能です。作れる関数の幅が広がった感じがして、
とても楽しいですね。

さて、オプショナルパラメータが呼び出し時に指定されたものなのか、
それともデフォルト値が設定されただけなのか知りたい場合があるそうです。
そういったときにはパラメータリストのデフォルト値の式のあとに
もう1つ変数の名前を追加すればよいそうです。
その変数は対応するパラメータが呼び出し元に指定された場合にT、
デフォルト値が設定された場合にNILとなります。
その変数の名前は規約により、対応するパラメータの名前に
"-supplied-p"をつけたものとしなくてはいけないようです。
提供されたか否か。わかりやすいです。

CL-USER> (defun hoge (a &optional (b 10 b-supplied-p))
		(unless b-supplied-p
		  (format t "デフォルト値が設定されました。"))
		  (* a b))
WARNING: redefining COMMON-LISP-USER::HOGE in DEFUN
HOGE
CL-USER> (hoge 4)
デフォルト値が設定されました。
40
CL-USER> (hoge 4 10)
40
CL-USER>

レストパラメータ

決まった数のオプショナルパラメータを設定する方法はわかりました。
次は引数は何個でも良いような関数はどう書けば良いのかです。

オプショナルパラメータを設定しまくる?流石に嫌ですね…。
引数の限界を知っている定数があるらしいので見てみます。

CL-USER> (prin1 call-arguments-limit)
4611686018427387903
4611686018427387903
CL-USER>

わーお。4611686018427387903個の変数名を含む
パラメータリストは書きたくありません。
(ところで参考書では、引数の最大数は最近の実装なら5億超ぐらいの
 ことが書いてあるので、PCの進化を感じますね。)
小芝居は終わりにして、シンボル&restと書くことで後ろのパラメータに
全部ひっくるめることができるそうです。

CL-USER> (defun my-list (&rest a) (list a))
MY-LIST
CL-USER> (my-list)
(NIL)
CL-USER> (my-list 1 2 3 4 5 6 7 8 9 0)
((1 2 3 4 5 6 7 8 9 0))
CL-USER>

便利ですね。挙動を見るに、引数は全て1つのリストになって
関数に渡されるみたいですね。

キーワードパラメタ

だいぶ色々なタイプの引数を持つ関数を作れるようになってきましたが、
まだ次のような場合には対応できません。
4つのオプショナルパラメータを持っていて、ほとんどの呼び出し元が
1つのパラメータしか設定せず、しかもみんな設定したいパラメータが
異なるような場合です。
最初のパラメータを設定する場合は問題ありませんが、
2~4つ目のパラメータだけを設定したい場合はどうしようもありません。

そんな時に使えるのがキーワードパラメータというものだそう。
必須パラメータと&optionalおよび&restの各パラメータのあとに
&keyというシンボルと任意の数のキーワードパラメタを指定する
識別子を含めることでキーワードパラメタを設定できるそうです。

CL-USER> (defun hoge (a &key (tasu 0  tasu-supplied-p)
                             (hiku 0 hiku-supplied-p)
			     (kakeru 1 kakeru-supplied-p)
			     (waru 1 waru-supplied-p))
	   (cond (tasu-supplied-p
		  (+ a tasu))
		 (hiku-supplied-p
		  (- a hiku))
		 (kakeru-supplied-p
		  (* a kakeru))
		 (waru-supplied-p
		  (/ a waru))
		 (t (princ "何か指定して"))))
WARNING: redefining COMMON-LISP-USER::HOGE in DEFUN
HOGE
CL-USER> (hoge 3 :tasu 6)
9
CL-USER> (hoge 3 :hiku 7)
-4
CL-USER> (hoge 3)
何か指定して
"何か指定して"
CL-USER>

日本語で足したり引いたりできます。
足し算した後に引き算したりできません。酷い。
オプショナルパラメータと同様にsupplied-pをつけられます。
勿論デフォルト値も設定できます。言及する順序が逆だった。

異なる種類のパラメータの併用

ここまで出てきたパラメータを一緒に使う場合は、
出てきたのと同じ順序で宣言していかなくてはいけないそうです。
必須→オプショナル→レスト→キーワード

大体は必須パラメータと他の何かということが多いようです。
オプショナルとレストが次に多いそうです。
オプショナルやレストとキーワードの併用は変な挙動になるかもとか。

オプショナルとキーワードの併用は絶対に避けるべきだそうです。
呼び出し元が全てのオプショナルパラメータを与えなかった場合、
オプショナルパラメータがキーワードパラメータのつもりの
キーワードと値を食べてしまうから予想外の結果になるということ。
あ〜、オプショナルパラメータが足りてないとき、:fooとかいう
キーワードをオプショナルパラメータだと思ってしまうんですね。
オプショナルとキーワードを併用したい時は、大体キーワードだけ
使っとけばいいとのことです。

レストとキーワードを両方使うと、&restの後ろにある
全てのパラメータはレストパラメータになり、
(:hogeとか:hugaも勿論レストパラメータに食われます)
:keyで指定されたパラメータはキーワードパラメータになるとのこと。
よっぽどの思惑がないと使わなさそうです。
基本は組み合わせないものと考えていきます。

今日は関数に渡すパラメータの話でした。
なかなかどうして奥が深いですね。
とはいえ使うの自体は難しくなさそうだし、作れる関数の幅が広がりました。
適切に使えるかどうかはまた別の話です…。
今日はここまでとします。