代入
これまでの説明でも代入を行う setq 関数は用いてきましたが、評価する/しないの説明をしたところで、改めて代入について二つの関数を紹介します。
AutoLISP関数 |
---|
(set 'symbol expr) |
symbol:シンボル expr:式 |
シングルクォーテーション付きのシンボル名の値に式を代入します。 |
戻り値:expr を評価した値 |
AutoLISP関数 |
---|
(setq symbol expr [sym expr]...) |
symbol:シンボル expr:式 |
ひとつまたは複数のシンボルに式の値を代入します。 |
戻り値:最後に評価されたexpr の値 |
二種類の代入の関数がありますが、一般的にシンボルに値を代入する際は setq 関数が用いられ、直感的で簡便です。setq は第一引数を式として「評価しない」というのが特徴です。「評価しない」という関数 quote と同じことが、他の大多数の関数とは異なり暗に第一引数に行われています。setq の q は quote の q です。戻り値は、セットした式の値が返ります。
_$ (setq a 100) ⏎
100
_$ (setq s "AutoCAD")⏎
"AutoCAD"
また、setq は、引数に「シンボル 値 シンボル 値 シンボル 値 …」と並べることで、複数の組み合わせを一度に代入することができます。複数ある場合の戻り値は、最後にセットした値になります。
_$ (setq geometry "circle" center (list 0.0 0.0 0.0) radius 100) ⏎ 100 _$ geometry⏎ "circle" _$ center⏎ (0.0 0.0 0.0) _$ radius⏎ 100
対して set は「内側優先、左側優先」に従って他の多くの関数のように第一引数を評価します。そのため、set を setq のように使うためには、第一引数にシングルクォーテーションを付加したり、あるいは長く(quote a)と書いたりする必要があります。
_$ (set 'a 100) ⏎
100
_$ (set 's "AutoCAD")⏎
"AutoCAD"
二種類の代入の関数の違いを見るために、シンボルにシンボル名が代入されているケースを考えます。
_$ (setq value "hello!") ⏎
"hello!"
_$ (setq pointer 'value) ⏎
VALUE
_$ value⏎
"hello!"
_$ pointer⏎
VALUE
_$ (eval pointer) ⏎
"hello!"
同じことを、第一引数を評価する set で行うと、以下のようなになります。第一引数にシングルクォーテーションを付加します。
_$ (set 'value "bye!") ⏎
"bye!"
_$ (set 'pointer 'value) ⏎
VALUE
_$ value⏎
"bye!"
_$ pointer⏎
VALUE
_$ (eval pointer) ⏎
"bye!"
ここまでは同じことを行っていますが、set 関数は第一引数が評価されるので、シングルクォーテーション付けないことで以下のような代入が行えます。
_$ (set pointer "welcome!") ⏎
"welcome!"
_$ (eval pointer) ⏎
"welcome!"
_$ value⏎
"welcome!"
上の set 関数では第一引数が評価され、シンボル pointer を評価した結果の value というシンボル名を得て、そのシンボルに値が代入されています。
破壊的関数
Common Lisp には、他にsetf という代入の関数があり、setq 関数より一般的に使われています。これは、引数がリストだった場合にコピーではなく、その内容を直接書き換えるもので、こういったものは「破壊的関数」と呼ばれています。リストの中の一部を書き換える時などで、プログラムの実行効率は上がりますが、思わぬ作用を引き起こすことがあるので、AutoLISP では、一切の破壊的関数は排除されています。
等価
LISP の等価・等しいという概念には特殊な区別があります。すなわち「同一性」と「同値性」です。等しいことを調べる関数には、これらの概念に対応した二つの種類があります。
AutoLISP関数 |
---|
(eq expr1 expr2) |
expr1,expr2:式 |
2 つの式が同一物かどうかを調べます。 |
戻り値:nil、またはnil 以外 |
AutoLISP関数 |
---|
(equal expr1 expr2 [fuzz]) |
expr1,expr2:式 fuzz:実数 |
2 つの式の評価結果が等しいかどうかを調べます。 |
戻り値:nil、またはnil 以外 |
eq 関数も equal 関数も、expt1 引数と expt2 引数とを比較して等しいかどうか判断します。ただし、eq 関数は「同一性」について検査を行い、equal 関数は「同値性」について検査を行います。
二つの関数は、アトム同士を比較している場合は同じように動作します。文字列の比較は大文字小文字が区別されます。
_$ (eq 2.0 2.0)⏎
T
_$ (equal 2.0 2.0) ⏎
T
_$ (eq 2.0 (+ 1.0 1.0))⏎
T
_$ (equal 2.0 (+ 1.0 1.0)) ⏎
T
_$ (eq "AutoCAD" (strcat "Auto" "CAD"))⏎
T
_$ (equal "AutoCAD" (strcat "Auto" "CAD"))⏎
T
二つの関数に違いが出てくるのは、リストを比較する場合です。まず、二つのシンボルが「同一のリスト」を指している場合です。この場合は動作が同じです。
_$ (setq a '(1 2)) ⏎
(1 2)
_$ (setq b a) ⏎
(1 2)
_$ (eq a b) ⏎
T
_$ (equal a b) ⏎
T

次に、「同一の内容」の二つのリストが存在している場合です。
_$ (setq a '(1 2)) ⏎
(1 2)
_$ (setq b '(1 2)) ⏎
(1 2)
_$ (eq a b) ⏎
nil
_$ (equal a b) ⏎
T
上の例では、同じ (1 2) というリストでも、異なる別のリストがそれぞれシンボル a b に代入されています。そのため、eq 関数では同一の内容でも異なるリストであるので nil が返ります。

まとめますと、アトムの比較では差はありません。リストが同一のものかどうかを調べるのは eq 関数を使います。eq 関数は「同一性」の比較を行います。リストの内容が等しいか調べる場合は、equal 関数を使います。equal 関数は「同値性」の比較を行います。
equal 関数は、ごくわずかな不確かさを許容して値が等しいかどうか判断することができます。座標の計算などを行うと、倍精度浮動小数点の実数でも大変小さな不確かさを生じます。この不確かさは10 の‐14 乗などといった、極めて小さい現実には無視できる不確かさです。こういった状況で完全に一致を求めると実用上の問題が生じます。
fuzz 引数には、差異の許容値を指定します。実用上は問題ない違いを無視することができます。
_$ (setq a 1.00001)⏎
1.00001
_$ (setq b 1.00006) ⏎
1.00006
_$ (equal a b (expt 10.0 -4)) ⏎ ;10の-4 乗以下の差異は無視します
T
_$ (equal a b (expt 10.0 -5)) ⏎ ;10の-5 乗以下の差異は無視します
nil
リストに格納された、例えば 3 次元の座標であっても同じです。
_$ (setq pt1 '(0.0 0.00001 0.0)) ⏎
(0.0 1.0e-005 0.0)
_$ (setq pt2 '(0.0 0.00006 0.0))⏎
(0.0 6.0e-005 0.0)
_$ (equal pt1 pt2 (expt 10.0 -4))⏎ ;10の-4 乗以下の差異は無視します
T
_$ (equal pt1 pt2 (expt 10.0 -5))⏎ ;10の-5 乗以下の差異は無視します
nil
三つの等価を調べる関数
eq 関数と equal 関数に加えて、AutoLISP には = という等価を調べる関数があります。= 関数については比較関数のところで改めて取り上げますが、比較の方法は eq 関数と同じ「同一性」の評価となります。最初は、これら三つの関数のどれを使えば適しているか迷うところです。リストの比較を行おうとしている場合は、同値性を検証するケースがほとんどだと思いますので、リストの比較は equal 関数を使うのを基本にした方がいいでしょう。アトムの場合は、読みやすさや、使い勝手から、= 関数が適していると思います。
三つの関数の特徴をまとめると以下のようになります。
関数 | 特徴 |
---|---|
eq | リストの比較の種類:同一性 「同一性」を検証するという特別な目的の時に使用します。 |
equal | リストの比較の種類:同値性 リスト要素の値の比較に適しています。許容誤差を指定できます。 |
= | リストの比較の種類:同一性 アトムの比較に適しています。二つ以上の引数をとることができ、それらが等しいか検査できます。実数の比較においては、誤差の有無について配慮が必要です。 |
コメント
AutoLISP のコメントは、ブロック単位と行単位の二種類があります。ブロック単位のコメントアウトは、コメントアウトしたい部分を「;|」と「|;」で囲みます。行単位のコメントは、;(セミコロン)から行末までがコメントとしてみなされます。
(setq a ;|ブロック単位のコメント|; 10)
(setq a 10) ;ここから行末までコメント
Visual LISP の整形機能では、行単位コメントのセミコロンの数が 1 から 3 まででインデントのされ方が異なります。インデントのされ方の違いは Visual LISP の解説の解説を見てください。
メタプログラミング
以上がここで LISP のコンセプトと呼んでいる言語の根幹部分になります。プログラミング言語としては関数の定義や条件分岐といった他のプログラミング言語でも共通のものが以降の章に出てきますが、しばしばそれの説明にページを割いてしまって述べられない、しかしながらまず抑えておきたい LISP の核となる部分です。再度繰り返してまとめますと、アトムからリストが作られます、リストはそのままではデータですが、評価を行うことによって実行できます。理解してしまえば単純な仕組みですが、これを言い換えるとプログラムでプログラムを生成して実行できるということを意味します。
それがどう役に立つのでしょうか?正直、これを知らないでも、使わないでも LISP のプログラミングはできます。しかし、これはメタプログラミングという視点を新しくプログラマーにもたらします。このことが LISP の方言の scheme が教育用途でよく使われると言われる理由の一つでしょう。
プログラムの生成はまさにプログラムを実行している時に行われます。そして関数の合成などといった関数型言語特有のアイデアを考えることが可能になります。手続き型言語ならば if 文で条件分岐しながら実行する複雑な経路のプログラムも、LISP ならまず if 関数などで条件を見ながら関数を並べたリストを生成し、それからそれを実行するというような構成とすることも可能です。条件分岐と実行を別の段階として分けるプログラムは、各所でデバッグが可能になり、部品として扱える関数の集合体として見通しの良い、変更や拡張のしやすいプログラムになります。
また、プログラムを生成するプログラムは、ある時は新しいプログラミング言語を作っているのと同じことを意味します。LISP の解説書ではしばしば LISP で LISP を作るという例が出てきます。とりかかりで速く LISP を覚えたいと思っているときには、正直意味がわかりません。しかしながら、LISP で LISP を作った場合は LISP のコードを読んで、より LISP の基本的で原始的な関数からなる実行可能コードを生成して実行している訳ですが、読むデータがプログラム中に埋め込まれた特殊な文字列だったり、ユーザーの入力やテキストファイルから読み込んだ何かのデータでも良いわけです。LISP はプログラマーが書いたプログラムと生成されたプログラムとデータとを必要に応じて使い分けながら転換可能なメタな思考をプログラマーにもたらします。
そうは言っても当面このことを何に使っていいのか分からないかもしれません。しかし、ある時複雑なプログラムを書かなければならなくなったときに、このことを思い出すと、手続き型言語ばかりを使っていては出来そうになかったワンランク上のプログラムが書けるかもしれません。冒頭で述べたとおりこの LISP のコンセプトは、LISP という言語の潜在能力を最大限使いこなす分水嶺となるものです。
LISP に初めてふれるのならば、ここで深く悩まず先に進んでも良いかと思います。以降に出てくる関数の定義などで LISP のコンセプトが頭の隅にあると理解がより深くなり、そこから返って LISP のコンセプトの有用性も実感していくことになるかと思います