common-lisp
フォームのグループ化
サーチ…
グループ化はいつ必要ですか?
Common Lispのいくつかの場所では、一連のフォームが順番に評価されます。例えば、 defunまたはlambdaの本体、または点線の本体でそのような場合、複数の書式を順番に書くことは、期待通りに機能します。しかし、 if式のthenとelse部分のようないくつかの場所では、単一の形式しか許されません。もちろん、それらの場所で複数の式を実際に評価したいかもしれません。そのような状況では、暗黙的な明示的なグループ化形式が必要です。
Progn
汎用特殊演算子prognは、0個以上のフォームを評価するために使用されます。最後のフォームの値が返されます。例えば、以下では、 (print 'hello)が評価され(その結果は無視されます)、 42が評価され、その結果( 42 )が返されます:
(progn
(print 'hello)
42)
;=> 42
progn内に書式がない場合、 nilが返されます。
(progn)
;=> NIL
prognは、一連のフォームをグループ化するだけでなく、 prognフォームが最上位フォームの場合、その中のすべてのフォームが最上位フォームとして処理されるという重要な特性も備えています。これは、複数のフォームに展開され、すべてが最上位フォームとして処理されるマクロを記述するときに重要になります。
Prognは、最後のフォームのすべての値を返すという点でも有益です。例えば、
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
対照的に、いくつかのグループ化式は結果生成フォームの主な値のみを返します。
暗黙の予言
一部の形式では、 暗黙的な予測を使用してその動作を記述します。例えば、 whenおよびunlessフォームは暗黙のprognの面で彼らの振る舞いを記述場合は 、基本的に偏っているマクロ。これは、
(when (foo-p foo)
form1
form2)
が評価され、条件(foo-p foo)が真の場合、 form1とform2はあたかもprogn内に含まれているかのようにグループ化されます。 whenマクロの展開は本質的に次のとおりです。
(if (foo-p foo)
(progn
form1
form2)
nil)
Prog1およびProg2
多くの場合、複数の式を評価し、最後の式ではなく最初の式または2番目の式から結果を返すと便利です。これはletとを使って簡単に実現できます:
(let ((form1-result form1))
form2
form3
;; ...
form-n-1
form-n
form1-result)
このフォームは、いくつかの用途では一般的であるので、Common Lispには、 PROG1とPROG2 のprognのようであるが、第1及び第2形態の結果を返します。例えば:
(prog1
42
(print 'hello)
(print 'goodbye))
;; => 42
(prog2
(print 'hello)
42
(print 'goodbye))
;; => 42
prog1と prog2とprognの重要な違いは、 prognは最後のフォームのすべての値を返しますが、 prog1とprog2は第1と第2のフォームのプライマリ値のみを返します。例えば:
(progn
(print 'hello)
(values 1 2 3))
;;=> 1, 2, 3
(prog1
(values 1 2 3)
(print 'hello))
;;=> 1 ; not 1, 2, 3
prog1形式の評価を持つ複数の値の場合は、代わりにmultiple-value-prog1を使用します。同様のマルチプルバリュープログ2はありませんが、必要に応じて実装するのは難しくありません。
ブロック
特殊演算子ブロックは、(暗黙のprogn
ような)いくつかのLispフォームのグループ化を可能にし、ブロックに名前を付けるための名前も取ります。ブロック内のフォームが評価されると、特殊演算子return-fromを使用してブロックを終了することができます。例えば:
(block foo
(print 'hello) ; evaluated
(return-from foo)
(print 'goodbye)) ; not evaluated
;;=> NIL
return-fromには戻り値も指定できます。
(block foo
(print 'hello) ; evaluated
(return-from foo 42)
(print 'goodbye)) ; not evaluated
;;=> 42
名前付きブロックは、コードの塊が意味のある名前を持つ場合、またはブロックがネストされている場合に便利です。状況によっては、ブロックから早期に復帰する能力だけが重要です。その場合、ブロック名としてnilを使用して戻り値を返すことができます。 戻り値は、ブロック名が常にnilであることを除いて、 return-fromと似ています。
注:囲まれたフォームはトップレベルのフォームではありません。それは異なっていprogn
トップレベルの囲まれたフォーム、 progn
フォームがまだトップレベルのフォームを考えられています。
タグボディー
グループフォームでたくさんのコントロールを行うには、 タグボディの特別な演算子が非常に役に立ちます。 タグ ボディフォーム内のフォームは、 goタグ (シンボルまたは整数のみ)または実行するフォームのいずれかです。 tagbodyの中で、 行く特別な作業は、新しい場所に実行を転送するために使用されます。このタイプのプログラミングは、任意の実行パスを可能にするので、かなり低レベルとみなすことができます。以下は、for-loopがtagbodyとして実装されたときのようなものの冗長な例です:
(let (x) ; for (x = 0; x < 5; x++) { print(hello); }
(tagbody
(setq x 0)
prologue
(unless (< x 5)
(go end))
begin
(print (list 'hello x))
epilogue
(incf x)
(go prologue)
end))
tagbodyとgoは一般的に使用されていませんが、おそらく "GOTOは有害なものとみなされます"が原因ですが、ステートマシンのような複雑な制御構造を実装するときに役立ちます。多くの反復構造体は暗黙のタグボディにも拡張されています。たとえば、 dotimesの本文は一連のタグとフォームとして指定されます。
どの形式を使用するのですか?
グループ化が必要なフォームに展開するマクロを書くときには、どのグループ化構造を拡張するかを考えておくと時間がかかります。
その子フォームがあるので、定義スタイルのフォームは通常、トップレベルのフォームとして表示されます、例えば、 定義ウィジェットマクロ、およびいくつかの関数定義 s のもの、defstruct Sなどのために、それは通常、 のprognを使用することは理にかなっていますトップレベルのフォームとして処理されます。繰り返しフォームの場合、暗黙のタグボディがより一般的です。
たとえば、 dotimes 、 dolist 、およびdoの本体は、暗黙のタグボディに展開されます。
コードの名前付き "チャンク"を定義するフォームでは、暗黙的なブロックが便利なことがよくあります。例えば、 defunの本体は暗黙のprognの内部にありますが、暗黙のprognはその関数の名前を共有するブロック内にあります。つまり、 return-fromを使用して関数を終了することができます。そのようなコンプ