関数

関数名と変数名の名前空間

Common Lisp は、変数名と関数名の名前空間は内部で分けられています。そのため、同じ名前をもつ関数と変数が存在することができます。それを区別するために、関数名を表すシンボルは、変数名のものと扱いを変えなければなりません。その扱いで異なる一つが、function 関数です。

Common Lisp では、以下のように function 関数は関数の中身を返します。

> (function sqrt) ⏎
#<Interpreted-Function C66ACE>

AutoLISP では、変数名と関数名は分けられておらず、同じ名前空間を共有するので、関数名でも変数名のように扱えます。

_$ sqrt⏎
#<SUBR @000000002e23cc00 SQRT>

function 関数は AutoLISP にも存在しますが、最適化を促す目的で用いられます。

さて、関数名と変数名の名前空間が分けられていない場合は、次のようなことが起こり得るので注意してください。

sqrt 関数は定義済みの関数ですが、関数定義のところで sqrt というローカル変数を宣言してもエラーにはなりません。このローカル変数は、任意の値を代入して使われることでしょう。ここで関数の sqrt を呼ぶときはどうなるでしょうか。答えは重複したローカル変数が無効になるまで元の関数は呼べない、です。仮に呼び出そうとした場合は実行時エラーとなります。正確には別の有効な関数が sqrt というシンボルに代入されていない限り、実行時エラーになります。このような場合でも、関数を表すsqrt というシンボルはグローバル変数で保管されていますので、ローカル変数が解放されれば、sqrt という関数が再び使えるようになります。

関数を返す関数

関数型言語では、lambda 関数を使って作成した関数そのものを数値や文字列と同じように関数の戻り値とすることができます。そうやって得られた関数は、適当なシンボルに代入することによって利用することができます。関数名を表すシンボルと関数そのものは、数値や文字列をシンボルにいれて扱っているのと同じように代入することによって関連づけられています。次は。与えられた引数に1 を加える関数を生成し関数の戻り値として返します。

(defun makePlusOne ( / )
(lambda (n) (1+ n))
)

使用例は以下のようになります。返ってきた匿名の関数に「plusOne」という名前を付けて保存し、以降で通常の関数のように使用できます。

_$ (setq plusOne (makePlusOne)) ⏎
#<USUBR @00000000022f53b8 -lambda->
_$ (plusOne 1) ⏎
2
_$ (plusOne 5) ⏎
6

さて、Common Lisp 言語の解説書ではここで「レキシカルスコープ」や「クロージャ」という話題が出てきます。

(defun makeAdder (n)
(lambda (x) (+ x n))
)

Common Lisp 上でのこの関数の意図を解説しておきますと、makeAdder 関数で関数を生成するとき、引数 n で指定した値により生成する関数をカスタマイズできるようにしたものです。レキシカルスコープ(lexical scope)の Common Lisp では、生成される関数内では変数 n の値は関数が作成された時点での値にクロージングされています。lexical という英単語は一般には「辞書の」や「(ある著者の)語彙の」といった意味です。スタティックなローカル変数に似ています。クロージャは、関数を定義する際に生成されます。クロージングされた変数と値は関数とセットで扱われます。

例えば、Common Lisp では以下のように使用します。変数 n に 100 が代入されていても影響がありません。

> (setq n 100) ⏎
100
> (setq add3 (makeadder 3)) ⏎
#<CLOSURE :LAMBDA (X) (+ X N)>
> (add3 7) ⏎
10

一方、AutoLISP はダイナミックスコープであり、関数を生成した時点での変数 n の値には依らず、関数を実行した時点での変数 n の値に依ります。AutoLISP で同じコードを書いて使用すると、以下のようにふるまいます。

_$ (setq n 100) ⏎
100
_$ (setq add3 (makeadder 3)) ⏎
#<USUBR @000000002d9596b0 -lambda->
_$ (add3 7) ⏎
107

変数 n のクロージングを利用することはできませんが、AutoLISP でもカスタム関数を生成することはできます。上の例では次のようなコードになります。

(defun makeAdder (n /)
(eval (list 'lambda '(x) (list '+ 'x n)))
)

関数を生成するときに変数 n の値を評価してしまい、匿名の関数の定義に埋込んでしまうことで生成する関数がバリエーションを持つことができるようになっています。すなわち(makeadder 3)が呼ばれると、(lambda (x) (+ x 3)) というリストが作成され、eval 関数で評価を行うことにより関数が生成されます。実行結果は以下のようになります。

_$ (setq n 100) ⏎
100
_$ (setq add3 (makeadder 3)) ⏎
#<USUBR @000000002d9597f0 -lambda->
_$ (add3 7) ⏎
10

関数の実⾏

関数の呼び出しに際して、関数に与えたい引数がリストとしてまとまっている場合には apply 関数が便利です。

AutoLISP関数
(apply 'function list)
function:関数
list:リスト
指定された関数にリストを引数として渡し、その関数を実行します。
戻り値:関数を評価した値

function 引数には関数名のシンボルを指定します。

list 引数には、引数をリストにまとめて指定します。引数が無い場合は「nil」とします。nil は空のリストを意味します。

戻り値は、関数を実行した結果の値です。function 引数が nil など無効だった場合はエラーが発生します。

シンプルな使い方は以下のとおりです。関数名はクォートされていること、引数はリストでなければならない点に注意してください。引数が一つの場合もリストとしてカッコで囲みます。

_$ (apply 'expt '(3 2))⏎      ;(expt 3 2)と同じ
9

lambda による匿名の関数を実行することもできます。

_$ (apply (function (lambda (x) (* x x))) '(2)) ⏎
4

なお、これは apply 関数を使わない場合は以下のように書けました。

_$ ((lambda (x) (* x x)) 2) ⏎
4

次の例は、整数のリストの合計を求めています。

_$ (apply '+ '(0 1 2 3 4 5)) ⏎      ;(+ 0 1 2 3 4 5)と同じ
15

apply 関数は、関数を引数に持つ高階関数の一種です。その他の高階関数については項を改めて説明します。

apply 関数でわざわざ関数を実行する必要性をあまり感じないかもしれませんが、ある条件下で関数を実行したいなどといったアイデアをもたらします。次の例の add-n 関数と mul-n 関数は引数に n を足したり掛けたりする関数です。n の値はダイナミックスコープで何の値かは定まっていません。それとセットで使う apply-n=10 関数は apply 関数と同じ呼び出し方で n の値を 10 にセットしてから与えられた関数を与えられた引数で実行します。

(defun add-n (a) (+ a n))
(defun mul-n (a) (* a n))

(defun apply-n=10 (func args / n)
  (setq n 10)
  (apply func args)
)

具体的な使用例は以下のようになります。

_$ (apply-n=10 'add-n '(5))⏎
15
_$ (apply-n=10 'mul-n '(5))⏎
50

この apply-n=10 関数で注目してもらいたいのは、例では (setq n 10) ですが特定の条件あるいは前処理のあとに関数を実行すること。そして apply 関数の形式の引数とすることで引数の数に関わらずどのような関数でも実行できることです。例にはありませんが後処理も含めることができます。AutoCAD のプログラミングにおいては、システム変数をある値にして関数を実行するといった応用も効くアイデアです。

遅延評価

遅延評価は関数型言語を学ぶと出てくる話題ですが、AutoLISP の標準関数の一部はもとより手続型言語の一部の機能で使われている概念です。それは、関数の引数は関数が実行される前に評価され値が定まったものが関数に渡されますが、その原則をパスして引数の評価を後回しにするという考えです。例としては、AutoLISP の and 関数、対応する手続型言語の && といったオペレーターといったものがあげられます。and 関数はすべての引数が nil 以外であった場合に nil 以外を返しますが、and 関数が実行される前に引数が評価されるわけではなく、and 関数が引数をひとつづつ評価しながら nil となる引数が見つかったら残りの引数は無視して即座に nil を返します。こういった関数も LISP ならば新しく定義が可能です。

具体的な例を示します。default 引数は第一引数が nil ならば第二引数を評価して返す関数です。第一引数が nil 以外ならばその値をそのまま返します。ここで第二引数を評価するというのがポイントです。

(defun default (value default-exp)
(if (null value)
(eval default-exp)
value
)
)

ユーザー入力などで未設定の値がある場合にデフォルト値を与える場合などで使用します。単純な使用例は次の通りです。ここでは default-exp 引数にアトムを渡しているので、未評価でも評価されても違いは出ません。

_$ input⏎
nil
_$ (default input 10)⏎
10
_$ (setq input 100)⏎
100
_$ (default input 10)⏎
100

次の例は、変数にフォルダーのパスが設定されていればその値を、未設定の場合はユーザーのドキュメントフォルダを返すという想定の使用例です。

_$ folder-path⏎
nil
_$ (default folder-path '(getvar "MYDOCUMENTSPREFIX"))⏎
"C:\\Users\\mchair\\Documents"
_$ (setq folder-path "D:\\data")⏎
"D:\\data"
_$ (default folder-path '(getvar "MYDOCUMENTSPREFIX"))⏎
"D:\\data"

ここで第二引数がクォートされた式を表すリストである点に注目してください。default 関数にはリストのまま渡されます。先に例に挙げた and 関数はクォートを省略して書く特殊形式です。そして、第一引数が nil だった時に初めてリストが評価され、具体的には getvar 関数がシステム変数の値を読み取ります。この式は progn 関数も使って複雑なものを指定することもできますが、デバッグのトレースが難しくなるので、複雑なものは関数にまとめてその関数を呼び出すリストを与えた方が良いでしょう。

この遅延評価のアイデアは、エラー処理を表す式をリストの形で指定して関数を実行するとか、ループなどの決まりきった形式処理の中で一部分だけ特定の処理を指定して渡すなど広く応用が効くものです。

次のような関数が定義してあったとします。ループで何を行うかは expression 引数で指定されます。

(defun Repeat-10-times (expression) (repeat 10 (eval expression)))

実際に使ってみた例は次の通りです。非常に単純な例ですが、プログラムを書いていて複数の個所でほとんど同じコードなのだけれども、ほんの一部分だけ違うという部分が出てくることはよくあることです。その違う部分だけを外部からリストの形で指定すれば、一つのコードで多くのことができるようになります。

_$ (Repeat-10-times '(princ "Hi! "))
Hi! Hi! Hi! Hi! Hi! Hi! Hi! Hi! Hi! Hi! "Hi! "

前出の apply 型の関数の呼び出し形式も、ここのリストの形で式を渡すことで実現することもできます。難を言えば、こちらの方が自由度が高すぎることです。

その他の関数をめぐるトピック

関数型言語あるいは LISP の関数が、手続型言語の関数と同じようでいて実はそのイメージに収まり切れないものであることを感じるようになっていただければと思います。

関数の操作について他にも話題がありますが応用のレベルになってくるので、この稿では触れずに別のコラムの方で紹介しています。そしてパズルを解くような、あるいはブロックを組み立てるような LISP 独特のプログラミングを楽しんでいただけたらと思います。