式の評価

評価の原理
Wolfram言語の基本操作は評価を行うことである.ユーザが式を入力すると,Wolfram言語は式を評価し,結果を返す.
Wolfram言語における評価は一連の定義を適用していくことで行われる.定義にはユーザが明示的に入力したものと,Wolfram言語に組込み済みのものがある.
例えば,Wolfram言語は整数の加法用の組込み関数を使い,式6+7を評価する.同じように,簡約化のための組込み関数を使い,代数式x-3x+1を評価する.その際に,x=5との定義がすでに作成されていれば,Wolfram言語は,この定義を使いx-3x+1-9に約す.
Wolfram言語において最も中心をなす2つの概念は「式」と「評価」であろう.「式」で説明したように,Wolfram言語が扱うオブジェクトは多岐に渡るが,それらのすべては,「式」を使い一様な形式で表される.ここでは,Wolfram言語が実行できるすべての操作を評価処理の例としてどのように統一的に見ることができるかを説明していく.
計算処理としての評価
5+611
簡約化処理としての評価
x-3x+11-2x
実行処理としての評価
x=55
評価の解釈例
Wolfram言語は,「無限評価システム」である.つまり,式が入力されると,Wolfram言語は知っている定義を適用できる結果が得られなくなるまで,定義を適用し続ける.
x1x2により定義し,その後に,x2を定義する:
x1が参照されると,Wolfram言語は,既知のすべての定義を使い結果を生成する:
階乗関数をそれ自身により定義した帰納的定義に適用する:
fac[10]が参照されると,Wolfram言語は,結果が変化しなくなるまで定義を繰り返し適用する:
Wolfram言語は,既知の定義を適用し尽くした時点で,得られた式をそれがどのようなものであれ結果として返す.場合によっては,結果は数等のオブジェクトである.しかし通常は,シンボル形式で表されたいくつかのオブジェクトを含む式である.
Wolfram言語は,加法式の簡約化のための組込み定義を使う.しかし,f[3]には何も変換規則の定義がないので,それはシンボル形式のまま返される:
Wolfram言語は,結果が変化しなくなるまで定義を適用していく,という原則に従う.このため,Wolfram言語が出力する最終結果を取り出し,再入力すると,同じ結果が再び得られる.(これに該当しない場合もある.詳しくは「無限評価の防止」を参照のこと.)
Wolfram言語からの結果をタイプすると,同じ式が再び得られる:
どんなときでも,Wolfram言語は,その時点で既知の定義しか使うことができない.もちろん,定義が後で追加されると,Wolfram言語はそれらも使うことができるようになる.ただし,その場合の結果は以前のものとは異なることもある.
関数fに関する新たな定義を作る:
新定義のために,得られる結果は変わることがある:
最も単純な評価の例として,f[x_]=x^2等の定義を使って,式を直接他の式に変換するという処理がある.しかし,Wolfram言語で書かれたプログラムの実行に使われる処理も評価の形態である.したがって,例えば,条件子やループを含んだ一連のWolfram言語式で構成された手続き型のプログラムがあるとき,それを実行することは式を評価することに相当する.場合によっては,評価処理は,例えば,ループにおいて特定の式を何回も評価することもある.
Print[zzzz]は,Do式の評価中に3回評価される:
式の標準形への還元
組込み関数によりその機能や動作の詳細は違う.しかし,多くの数学関数では1つの共通で重要なアプローチが使われている.つまり,それらはすべて数学の式を標準形に還元するという目的で機能している.
例えば,関数Plusに関する組込み定義は,標準形に従った項を丸カッコでくくらない形式で加法の項を書くように設定されている.加法の結合則によると,(a+b)+ca+(b+c)a+b+cは等しい.しかし,多くの用途では,これらの項すべてが単一標準形a+b+cに還元された方が便利である.Plusの組込み定義はこの変形操作を行うように設定されている.
Plusに関する組込み定義を介すことで,この式は丸カッコなしの標準形に還元される:
ある関数が結合則に適合するものと判明するとき,「平坦化」,つまり,不必要な丸カッコ(または,ネストしている関数頭部)の除去が行われて標準形への変換が図られる.
足し算のような関数には結合則の他に交換則も成り立つため,a+c+ba+b+cのように項の順序を入れ替えても値は等しいまま保持される.しかし,Wolfram言語では,このような式はすべて一律な「標準形」にされる.ここで,使われる標準形とは,すべての項を,アルファベット順に近い並び順に対応した一定の順序に置く形を言う.
足し算の項が標準的な順序に並び替えられる:
平坦性(結合則)
と等しい等
順不同性(交換則)
と等しい等
特定の関数を標準形に還元するときに使われる2つの重要な性質
式を標準形に変換するのには,いくつかの理由がある.第1の理由は,標準形になっていれば,評価する際に2つの式が同じものかどうか簡単に判別できる.
標準形に再構成されると,直ちに二項が同じものであることが判明する.2つのfは打ち消し合い,結果は0になる:
上の例で,項を可能な限りの並び順に変え,2つの構成を見比べることでa+c+bc+a+bが等しいかどうかを判断するのは可能である.しかし,そうするよりは,両方の式を標準形にしてから判定した方が処理能率はよくなる.
それならば,加法以外の数式もすべて自動的に一律な標準形に変換すれば,さらに能率が上がるのでは,と考えるかもしれない.しかし,式が非常に簡単なものでなければ,必ずしも同じ標準形には直したくないだろう.
例えば,多項式には2つの標準形が考えられるが,それぞれ違った目的に適する.その1つは,Expandを作用させることで得られる展開された項の単純和である.この標準形は多項式の加減法を行うのに最適な形である.
多項式の標準形はもうひとつある.Factorを適用することで,多項式を,それ以上は還元できない因数で作られた積として書くことができる.この標準形は除法等の演算に適している.
展開形と因数分解形はどちらも同等に重要な多項式の標準形である.どちらを使うかは,何にそれを適用させるかによる.このため,Wolfram言語では,多項式は,自動的にこれらの形のいずれかに変換されるということはない.その代りに,ExpandFactor等の関数が提供されているので,それらを使うことで,ユーザ自身が多項式の形を明示的に選択することができるようになっている.
数学的に等しい2つの多項式をリスト形式で入力する:
単にExpandを作用させるだけで展開形に変形させることができる.2つの式が等しいことが明白になる:
因数分解形に書き換えることでも,両方の多項式が等しいことが分かる:
式によっては,必ずしも一律な標準形に直したくはないことが上の例からも明白である.それならば,可能性として,すべての式は何等かの標準形に還元できるのだろうか.
この疑問に関する基本結果は数学の計算理論からすでに判明している.それによると,標準形に還元することは式によっては不可能である.2つの任意の式があるとき,限られた変換操作でこれらを標準形に整理できるという保証はない.
このことは,ある意味では,あまり驚くようなものでもない.どんな式でも標準形に直せるなら,式が等しいかどうかは簡単に判明させることができてしまう.しかし,数学の難解問題には,いわゆる式の同値性に関するものが数多くある.この事実からも,変換操作がいかに難しいものかが分かる.
属性
f[x_]=x^2等の定義は,関数に特定の値を指定する.場合によっては,明示的な値は与える必要がないかもしれないが,一般的な関数の性質は指定しなければならないことがある.
関数の性質は,属性で指定することができる.例えば,Flatと呼ばれる属性を関数に付与しておくと,その関数は「平坦」な特性を持つことになる.つまり,引数の部分がネストすれば,自動的にそのネスト部が平坦化され,結合則が適用可能な状態になる.
属性Flatを関数fに割り当てる:
ネスト構成が平坦化され,fは結合則が適用可能な関数になる:
Flatのような属性は評価の仕方だけでなくパターンマッチ等の操作にも影響する.このため,属性の指定は,関数を定義したり,変換規則を適用する前に行う必要がある.
平坦な関数fに関する定義を作る:
fは平坦なので,先の定義はすべての引数に自動的に適用される:
Attributes[f]
f の属性を参照する
Attributes[f]={attr1,attr2,}
f に複数の属性を一括で割り当てる
Attributes[f]={}
f を属性なしとする
SetAttributes[f,attr]
属性 attrf に加える
ClearAttributes[f,attr]
f の属性 attr を除去する
シンボルの属性操作
fに割り当てられた属性を確認する:
fの属性をすべて取り除く:
Orderless
交換則が適用する関数とする(引数は標準的順序に並び替る)
Flat
結合則が適用する関数とする(引数は平坦化される)
OneIdentity
関数をパターンマッチング用の引数の値に等しいとする(例:f[f[a]]a に等しい)
Listable
f は引数であるリストの各要素に同じ関数を適用した関数とする (例:f[{a,b}]{{f[a],f[b]})となる)
Constant
f のすべての階の導関数がゼロの関数とする
NumericFunction
f は,引数を数値とすれば,関数の返す値も数値になるような特性を持つ関数とする
Protected
f の値は変更不可
Locked
f の属性は変更不可
ReadProtected
f の値は参照不可
HoldFirst
f の第1引数は評価不可
HoldRest
f の第1引数を除くすべては評価不可
HoldAll
すべての f の引数は評価不可
HoldAllComplete
f の引数は完全に不活性なものとする
NHoldFirst
f の第1引数はNによる数値計算不可
NHoldRest
f の第1引数を除くすべてはNによる数値計算不可
NHoldAll
すべての f の引数はNによる数値計算不可
SequenceHold
(Sequence)オブジェクトからなる f の引数は平坦化不可
Temporary
f は不必要になると除去される局所変数として扱われる関数とする
Stub
f が明示的に入力されたときにはNeedsが自動的に呼び出される
シンボルの属性一覧
組込み関数Plusがどんな属性を持っているか調べる:
Wolfram言語の組込み数学関数に割り当てられている重要な属性にListable(リスト可)がある.この属性の関数に引数をリストとして与えると,関数頭部がリストの各要素に直接かかるように適用される.「糸を縫う」ように各リスト要素に分配されることから,この分配操作は英語でスレッド(thread)とも呼ばれる.
組込み関数Logは属性Listableを持つ:
関数pをリスト可能であると定義する:
pは自動的に引数中のリストの各要素に分配される:
多くの属性は,適用先の関数の評価そのものに直接影響を与える.評価には影響しないが,他の面で影響を与える属性もある.例えば,属性OneIdentityはパターンマッチだけに影響する(「平坦な関数と順不同な関数」を参照).同様に,属性Constantは微分を計算するときか,微分に依存する操作が必要なときだけに有効になる.
属性Protectedは割当て操作に影響する.プロテクトの掛かったシンボルは再定義することができない.「組込み関数の変更」で説明した関数ProtectUnprotectを,SetAttributesClearAttributesの代りに使っても,プロテクトの有効化と解除を行うことができる.「組込み関数の変更」で触れたが,Wolfram言語が起動した時点では,ほとんどの組込み関数にプロテクトが掛かっている.このため,ユーザが誤って定義付けをしようとしてもできない.
関数gを定義する:
gの属性をProtectedにする:
今度は,gの定義は変更することができない:
特定のシンボルに割り当てた定義を参照するには?f と入力する(組込み関数にもこれに関連した機能を提供するものが各種あるので,それらを使ってもよい).参照禁止の属性ReadProtectedを変数や関数に付加しておけば,定義内容が参照できないようにすることができるようになっている.参照不可になっても,関係式自体は有効なままであり,引き続き評価に使うことができる.
gの定義式を変更することはできないが,参照することは可能である:
属性ReadProtectedを割り当てて,gを参照不可にする:
今度は,gの定義内容は参照することができない:
SetAttributesClearAttributesのような関数を使えば,通常,変数や関数の属性をどのようにでも設定変更することができる.ただし,あらかじめ変数や関数に属性Lockedが割り当ててあるときは,属性の変更はできない.(新たに設けたWolframシステムセッションなら,ロックは解除されるので,この限りではない.)属性Lockedを属性Protectedと参照禁止の属性ReadProtectedに組み合せて使うことで,ユーザによる不用意な定義内容の参照や,変更を未然に防ぐことができる.
Clear[f]
f に割り当てられている値を消去する(属性はそのまま維持される)
ClearAll[f]
f の割当て値と属性をすべて消去する
値と属性の消去
上の例でpに属性Listableを与えたが,ここで,値と属性のすべてを消去する:
属性を取ってしまったので,pは展開することができない:
関数に属性を与えるということは,Wolfram言語に対して評価や参照で特別な性質を考慮するよう指示することを意味する.通常,一度与えられた性質は常に有効だが,場合によっては,限られた状況下でだけそれを有効にしたいかもしれない.そのようなときは,属性は直接使わずに,代りに,特殊な関数を呼び出すことでその属性に関連付けられた変換を行うことも可能である.
Threadを直接作用させ,pがリスト可能のときに自動的に行われる変換操作を強制する:
OrderlessSort[f[args]]
FlatFlatten[f[args]]
ListableThread[f[args]]
ConstantDt[expr,Constants->f]
属性に関連した変換操作を行うための関数
属性は単一シンボルに対してのみ恒久的に定義される.ただし,属性付きの純関数を構築することで,一時的にしか有効でない属性を備えた関数を定義することは可能である.
Function[vars,body,{attr1,}]
属性 attr1, を備えた純関数を定義する
属性を備えた純関数
この純関数では,pをリスト全体に適用する:
属性Listableを加えておくと,pがリストの各要素に分配されるようになる:
標準評価手順
ここでは,式の評価に使われるWolframシステムの標準的な処理手順を説明していく.この手順はほとんどの式に使われるが,プログラムや制御構造体を表した式では使われない.後者には別の手順が使われる.
標準評価手順では,まず,式の頭部が評価され,次に,式の各要素が評価される.式の各要素にも一般的な式と同様に扱われ同じ評価手順が帰納的に適用される.
関数Printは代る代る3回続けて評価される.評価されるたびに引数が表示され,最後に戻り値としてNullが出力される:
足し算の頭部Pluspsとする:
頭部psが先に評価されるので,通常の足し算として扱われる:
Wolframシステムは,式の頭部を評価し終えると,即座に頭部が属性を持ったシンボルかどうかを判定する.もしも,順不同性Orderlessや平坦性Flat,または,リスト可Listableの属性が備わっていると,Wolframシステムは,式の要素の評価が終了した時点で,これらの属性に関連した変換操作を行う.
手順の次の段階では,Wolframシステムは,これから評価していく式に対して,既知の定義を適用する.ユーザ指定の定義がまず適用され,次に,組込み済みの定義が適用される.
適用可能な定義が見付かると,それに対応した変換処理が式に対して施される.結果として,別の式が生成されるが,その式についても標準評価手順による処理が繰り返し施される.
式の頭部を評価する.
式の各要素を1つずつ評価する.
属性(順不同性Orderless,リスト可Listable,平坦性Flat)に関連した変換操作を適用する.
ユーザ指定の定義を適用する.
組込み定義を適用する.
結果を評価する.
標準評価手順
「評価の原理」で説明したが,Wolframシステムにおける評価の基本は,適用可能な規則がなくなるまで式の評価を続ける,ということである.つまり,結果が出たら,それに対して再び規則が適用される.そして,次の結果に変化がなくなるまで,評価処理は繰り返される.
簡単な例を見ながら,具体的な標準評価手順の進行を説明する.以下の説明では,a=7とする.
2ax+a^2+1
ユーザが式を入力する
Plus[Times[2,a,x],Power[a,2],1]
式が内部表記に変換される
Times[2,a,x]
まずこの項が評価される
Times[2,7,x]
aが評価され,あらかじめ定義された値7が代入される
Times[14,x]
式全体にTimesの組込み定義が使われ,積が評価される
Power[a,2]
この項が次に評価される
Power[7,2]
aに割り当てられた値7が代入される
49
式全体にPower用の組込み規則が使われ,ベキが計算される
Plus[Times[14,x],49,1]
評価済みの各項がPlus式に取り込まれる
Plus[50,Times[14,x]]
式全体にPlus用の組込み定義が使われ,和が計算される
50+14x
結果がこのように表示される
簡単な評価例
「評価処理のトレース」で触れるが,Wolframシステムにはトレースと呼ばれる評価処理の追跡機能が備わっている.Trace[expr]を式に作用させると,式の評価過程で生成される暫定的な内部式をネストされたリスト形式で表示することができる.(注意点として,標準評価手順では,式の木構造において最下位から上位に向かって評価は進められるので,式の最小単位の部分がTraceからの結果のリストに最初に現れる.)
a7としておく:
トレースさせると,式の評価過程で生成されたすべての副次的な式がネストされたリスト形式で表示される:
定義の適用順序は重要な意味を持っている.つまり,「組込み関数の変更」で触れたように,ユーザ指定の定義は組込み型の定義より優先されるため,組込み型の定義を代替するための定義を与えることが可能である.
この式の評価には,ArcSinの組込み定義が使われる:
ArcSinにユーザ指定の定義を与える.プロテクトを解除しておく必要がある:
今度は,ユーザ定義が優先される:
「異なるシンボルへの定義式の関連付け」で説明したが,規則を上向きの値または下向きの値としてオブジェクトに割り当てることができる.Wolframシステムは,上向きの値の定義を下向きの値の定義より優先させる.
f[g[x]]のような式には,通常,適用可能な定義には2つの形式がある.f に関連付けられた下向きの値,そして,g に関連付けられた上向きの値である.Wolframシステムは,g に関連付けられた定義を f の定義より優先させる.
この順序付けは,特殊な規則は一般的な規則より先に適用させる,といった指針に基づいている.関数に関連付けられた下向きの値の前に,引数に関連付けられた上向きの値を優先させることで,任意の引数を持つ関数のための一般定義を上書きする特別な引数に対する定義を作成することができる.
f[g[x_]]に関する規則をfに関連付けた形で定義する:
今度は,f[g[x_]]gに関連付けた形で定義する:
gに関連付けられた規則がfに関連付けられた規則より先に適用される:
gの規則を除去する.今度は,fの規則が使われる:
f[g[x]]において,g に関連付けられた定義は, f に関連付けられた定義より優先する.
定義の適用順序
Plusのような組込み関数の多くは下向きの値を持つ.ただし,Wolframシステムの組込み型オブジェクトによっては上向きの値を持つものもある.例えば,ベキ級数を表すのに使われるSeriesDataオブジェクトは,数学的操作に関した組込み済みの上向きの値を持つ.
f[g[x]]等の式における,標準評価手順に従い適用される一連の定義の優先順位は以下の通りである.
下向きの値より上向きの値の方が優先されるという事実は,いろいろな場面で重要な意味を持つ.例えば,式を再構成するための操作を定義するとする.再構成の操作に関する上向きの値を各種オブジェクトに対して与えると,これらの上向きの値はこのオブジェクトが現れるたびに使われる.しかし,特別なオブジェクトが見当たらないときに使うための,再構成用の一般的な手続きを与えることもまた可能である.その場合,この手続きは,再構成操作に関する下向きの値として与えることができる.下向きの値は上向きの値の後に試されるので,上向きの値を伴ったオブジェクトが何もないときに限って,一般手続きが使われる.
qオブジェクト」を再構成するための定義を作成し,qに関連付ける:
今度は,一般則を作り,compに関連付ける:
2つのqオブジェクトを再構成させる.すると,qに関連付けられた規則が使われる:
rオブジェクトを再構成させると,compに関連付けられた一般則が使われる:
一般に,式は上向きの値を備えた複数のオブジェクトを持つ.Wolframシステムは,まず,式の頭部を参照し,それに関連した上向きの値を試す.次に,式の各要素を順々に参照していき,要素に上向きの値があればそれを試す.この手順は,まずユーザ定義の上向きの値に対して行われ,続いて,組込み済みの上向きの値に対して行われる.また,この手順が一連の要素に対して適用されるとき,先に現れる要素に関連付けられた上向きの値は,後寄りの要素に関連付けられた上向きの値より優先される.
cに関連付ける形でpに関する上向きの値を定義する:
qに関する上向きの値を定義する:
どの上向きの値が適用されるかは,cの引数列においてどちらが先に現れるかによる:
非標準な評価手順
組込み関数のほとんどは標準的な評価手順に従って評価されるが,例外もある.例えば,プログラムの構築や実行に使われるほとんどの関数は,非標準の手順で処理される.このような関数では,引数の一部が全く評価されなかったり,引数は評価されても関数の制御下にあるため特別処理を受けたりするものもある.
x=y
式の左辺は評価しない
If[p,a,b]
条件式 pTrueなら式 a を評価し,Falseなら式 b を評価する
Do[expr,{n}]
exprn 回繰返し評価する
Plot[f,{x,}]
x の数値データについて式 f を評価する
Function[{x},body]
実際に関数の適用があるまでは評価しない
非標準手順により評価される関数
a=1のような定義を与えるとき,左辺に現れる a は評価されない.評価されると,困った状況に陥るだろう.なぜかといえば,例えば,先に a=7と割り当てておき,後で,a=1と直すと,前の定義が a に適用されてしまい,定義が7=1と無意味な形になってしまう.
標準評価手順において,関数の引数は式の先頭のものから順々に評価される.この処理を強制的に禁止するには,HoldFirstHoldRestHoldAllの式の評価を保留にする属性を式に設定しておく.
HoldFirst
第1引数は評価不可
HoldRest
第1引数を除くすべての引数は評価不可
HoldAll
すべての引数は評価不可
関数の引数を未評価の形に維持するための属性
標準評価手順が使われるので,関数の引数はすべて評価される:
第1引数のホールド属性HoldFirsth に与えておく:
h の最初の引数は評価されなくなる:
h の第1引数をこのようにして使うと,また,評価可能になる:
Set等の組込み関数では,第1引数のホールド属性HoldFirst等が付いている:
関数にホールド属性が付いていて,引数が評価保留の状態にあっても,Evaluate[arg]を引数に作用させることで強制的に評価を行うことができる.
Evaluateを使い,HoldFirstの属性を無効にすると,第1引数の評価が行われる:
f[Evaluate[arg]]
f にホールド属性があっても,引数 arg を強制的に評価する
関数の引数の強制評価
ホールド用の属性と強制評価の関数を組み合せることで,関数の引数評価をいつ実行したらよいかを制御することができる.引数にEvaluateを作用させておき,関数が参照されるのを待たずに,即座に評価させることも可能である.この方法はいろいろな場面で便利になる.
関数Setは,第1引数をホールドにしておく.このため,シンボル a は評価されない:
Setを使うが,今度は,Evaluateを第1引数である左辺に作用させ強制的に評価する.ab で置換され,その後に,b6に設定される:
b は確かに6になっている:
普通は,式を入力したら,そのすべてを評価したい.それでも,場合によっては,式を評価処理から外しておきたいときもあるだろう.例えば,Wolfram言語プログラムにおいて式のある部分にシンボル的な操作を施したいとする.そのようなとき,この操作が行われる間は評価保留にしておきたい.
特定の式を評価保留にするには,ホールド用の関数HoldHoldFormを使う.これらの関数は,適用される式に対して属性HoldAllを付加し,式を未評価のまま保持してくれる.
Hold[expr]HoldForm[expr]の違いは出力における表示的なものである.標準出力表記では,Holdの記述は表示されるが,HoldFormは表示されない.完全形に切り換えれば,両方とも表示される.
Holdを作用させると,式は未評価の形のまま保持される:
HoldFormも評価保留にできる.しかし,標準形的なWolfram言語の出力形式では表示されない:
完全形にすると,HoldFormの使用が確認できる:
ReleaseHoldを作用させると,HoldHoldFormのホールド機能は解除される.今度は,評価される:
Hold[expr]
expr の評価を保留にする
HoldComplete[expr]
expr の評価を保留にし,expr に関連付けられている上向きの値の適用を防止する
HoldForm[expr]
expr の評価を保留にし,HoldFormは記述せずに表示する
ReleaseHold[expr]
expr に適用されたHoldHoldForm の機能を解除する
Extract[expr,index,Hold]
expr の一部をHoldで包み込み,評価されないようにする
評価保留の式を扱うための関数
普通,抽出した部分はすぐに評価される:
今度は,抽出した部分をHoldで包み込み,評価不可にする:
f[,Unevaluated[expr],]
expr 未評価のまま引数として f に与える
引数評価の保留
評価すると,1+12になり,長さLength[2]0になる:
1+1を評価禁止にし,そのままをLengthに与える:
Unevaluated[expr]を使うと,実効的に,関数に対してHoldFirst等の属性が一時的に適用され,その後で,expr はこの関数に引数として与えられる.
SequenceHold
引数として現れるSequenceオブジェクトは平坦化しない
HoldAllComplete
全引数を完全に不活性なものとする
評価関連操作のホールド属性
全引数のホールド属性HoldAllを与えることで,関数の引数をすべて評価不可にすることができる.しかし,この属性を与えても,引数の変換処理が一部実行されてしまう.つまり,評価保留にする引数がSequenceオブジェクトのときは,平坦化処理が行われてしまう.これを禁止にするには,SequenceHoldの属性を適用する必要がある.また,引数に上向きの値が関連付けられていると,上向きの値が適用されてしまう.これを防ぐには,属性HoldAllCompleteを与えておき,評価禁止関数Unevaluatedが除去されないようにしておく必要がある.
パターン,規則,定義の評価
式の評価とパターンマッチングの処理では,いくつかの相互作用的な操作が必要になる.第一に,パターンマッチングでは,検索の対象となる式は少なくとも部分的に評価済みである.このため,パターン自体もあらかじめ評価しておいた方が都合がよい.
この例では,あらかじめパターンが評価される.このため,パターンは与えられた式にマッチする:
/;条件の右辺は,パターンマッチングに使われるまで未評価のままにされる:
場合によっては,パターンの全部もしくは一部を評価不可としておきたい.そうするには,HoldPatternを評価不可にしたい部分に作用させる.つまり,評価したくないパターンが patt ならば,HoldPattern[patt]と記述する.すると,パターンとしての patt の機能はそのまま保持されるが,パターン自体は未評価のままになる.
HoldPattern[patt]
パターンマッチングの機能上は patt と同じだが,patt では評価は保留にする
パターン評価の保留
HoldPatternの応用ケースとして,まだ評価されていない式,つまり未評価の形の式に対して適用するためのパターンの指定がある.
HoldPatternを作用させたため,1+1は評価不可になり,演算子/.の左辺にある1+1にマッチするようになる:
注意してほしいのは,ホールドHoldのような関数を使うと式の評価を保留できるが,このような関数は,/.や他の演算子を伴った式において,式の部分に対する操作には影響しないことである.
これは,rが引数を原子オブジェクトとしないときにその値を定義する:
r[3]のような式は条件に合わないので関数の定義は適用されない:
しかし,パターンr[x_]は,rの定義に従って変換される:
HoldPatternを作用させr[x_]を評価不可の形に変換しておかないと,先の定義が適用されてしまう:
上の例で示したように,通常,lhs->rhs のような変換規則において左辺 lhs はすぐに評価されてしまう.規則は,普通,すでに評価済みの式に対して適用されるからである.また,lhs->rhs における右辺もすぐに評価される.ただし,遅延型の規則 lhs:>rhs を使えば,右辺 rhs は評価しないまま保持しておくことができる.
->による規則は直ちに評価されるが,:>の規則は評価されない:
両方の規則を適用したらどうなるか見てみよう.:>規則の右辺は,未評価のままHold式に組み込まれる:
lhs->rhs
左辺 lhs と右辺 rhs をともに評価する
lhs:>rhs
左辺 lhs は評価するが,右辺 rhs はしない
変換規則における評価
変換規則の左辺は普通評価されるが,定義の左辺は普通評価されない.理由は次の通りである.変換規則は,通常,/.を介して評価済みの式に適用される.これに対して,定義は評価の過程で適用される.つまり,まだ完全に評価の済んでいない式に適用される.定義をこのような式に対して適用可とするには,定義の左辺を少なくとも一部未評価のまま保持しておく必要がある.
最も単純なケースとして,シンボルに対する定義がある.「非標準な評価手順」で説明したが,x=value の定義において,左辺にあるシンボルは評価されないまま残される.もしも,定義が与えられる前に,xy が割り当てられ,その上で,x=value により左辺が評価されることになったなら,y=value といったおかしな定義が発生してしまう.
定義を作る.左辺のシンボルは評価されない:
シンボルを定義し直す:
左辺を強制的に評価する.すると,シンボルkではなく,シンボルkの値であるw[4]が定義される:
w[4]の値はw[5]になっている:
定義の左辺にある個々のシンボルは評価されないが,左辺が複雑な式からなるときは,それらが部分的に評価されることもある.例えば,左辺が f[args]のような形を取るとき,引数部 args は前もって評価される.
1+1はあらかじめ評価される.このため,値はg[2]に対して定義される:
gに定義された値を確認する:
定義の左辺に現れる関数において,その引数部を先に評価しなければならない理由は,ある式の評価においてこの定義がどう使われるかを考えるとよく分かる.「評価の原理」で説明したように,関数が評価されるときは,関数の持つ引数がまず評価の対象になる.その次に,関数に関連した定義が検索され,見付かればそれが適用される.つまり,定義を関数に適用する前に,引数はすでに評価済みでなければならない.ただし,例外が1つある.それは,対象となる関数が特別な属性を備えており,引数の評価が保留になっているときである.
symbol=value
変数 symbol は評価しないが,値 value は評価する
symbol:=value
変数 symbol と値 value はともに評価しない
f[args]=value
引数 args は評価するが,左辺全体は評価しない
f[HoldPattern[arg]]=value
引数 arg の評価なしで f[arg]が割り当てられる
Evaluate[lhs]=value
左辺を完全に評価する
定義における評価
定義の左辺に現れる関数において,その引数が先に評価されることは,普通,適切である.しかし,状況によっては,そうしたくない場合もある.そのようなときは,評価不可としたい部分に対してHoldPatternを作用させておく.
反復関数における評価
反復操作を行うTableSum等の組込み関数において,引数評価は多少特別の扱いを受ける.
Table[f,{i,imax}]等の評価で取られる最初のステップでは,i の値を局所的にすることが行われる(「ブロックと局所値」を参照のこと).次に,反復回数の上限 imax が評価される.式 f は,i の値が確定するまで未評価なまま保持される.反復評価がすべて終了したとき,変数 i にはもとの大域値が戻される.
乱数生成関数RandomReal[]を4回実行する.毎回,別の擬似乱数が生成される:
Tableを作用させる前にRandomReal[]を評価しておく.すると,同じ値が4回出力されるだけになる:
Table[f,{i,imax}]のような反復式では,普通,i に特定の値が割り当てられるまでは関数 f は未評価な形のままにしておいた方がよい.任意の値を持つ i に対して有効となる関数 f の完全なシンボル形式が見付からないときは特にそうである.
引数が整数なら階乗を計算し,そうでない場合はNaN(英語で「数でない」という意味の「Not a Number」の略)を返す関数を定義し,facと呼ぶ:
i に整数が割り当てられるまで,fac[i]は評価されない:
Evaluateを使い,i がシンボル的なオブジェクトであってもfac[i]を強制評価させる:
Table[f,{i,imax}]のような式において,f を,任意な i を持つ関数として完全なシンボル形式で記述することが可能であれば,このシンボル形式をまず計算してしまい,その後にTableを作用させた方が計算の効率が上がる.これを行うには,Table[Evaluate[f],{i,imax}]を使う.
i に新たな値が割り当てられるたびにSum式は評価される:
実は,この和に相当するシンボル形式の論理式を得ることができる.それは,i がどんな値であっても有効である:
Evaluateを挿入しておくことで,Wolfram言語にまず和をシンボル的に評価させ,次に,i 上で反復させる:
Table[f,{i,imax}]
i に特定な値が割り当てられるまでは f を未評価なままにしておく
Table[Evaluate[f],{i,imax}]
i は未知数としておいて,f を強制的に評価する
反復関数における評価
条件子
Wolfram言語では,特定条件が満たされるときだけに特定の式を評価させるための指定を行うことができるようになっている.これは,次に示す方法で条件子を設定することで行う.
lhs:=rhs/;test
判定式 test の評価結果がTrueのときだけ定義を有効にする
If[test,then,else]
判定式 testTrueなら式 then を,Falseなら式 else を評価する
Which[test1,value1,test2,]
判定式 testi を順に評価していき,最初にTrueになる判定式に対応した値を返す
Switch[expr,form1,value1,form2,]
expr を形 formiと比較していき,最初にマッチする形に対応した値を返す
Switch[expr,form1,value1,form2,,_,def]
def をデフォルト値として使う
Piecewise[{{value1,test1},},def]
Trueを与える最初の testi に当たる値を返す
条件文
判定式はFalseを与える.このため,「else」に対応する式yが返される:
この場合は,「else」式だけが評価される:
Wolfram言語によるプログラムの記述で分枝制御を行うには基本的に2つの方法を使う.1つ目は,単一定義を作り,右辺にIf関数による選択肢を与える方法で,2つ目は,複数の定義を作り,各定義を適切な/;条件で制御する方法である.複数の定義を使った方が,プログラムを読みやすくでき,変更も簡単になる.
ステップ関数を定義する.値はx>0のときに1で,それ以外は-1とする:
/;条件を使い,ステップ関数の正の部分を定義する:
これは,関数の負の部分を定義する:
/;条件を使った定義をすべて表示させる:
選択肢が2つありどちらかを選択するには関数Ifを使うことができる.しかし,選択肢がさらにある場合もある.そのようなときの方法として,ネストさせた複数のIf関数を使うことが考えられる.しかし,普通は,WhichSwitch等の関数を使う方がよい.
3つの定義域を持つ関数を定義する.第3条件をTrueとすることで,第1と第2定義域に入らないときのデフォルトを与えておく:
これにはWhichの第1ケースが使われる:
第3ケースが使われる:
3を法とした引数が取る値に応じて戻り値が変わる関数を定義する:
Mod[7,3]1だから,Switchの第2ケースが使われる:
170にも1にもマッチしない.しかし,パターン_にはマッチする:
これは重要なことだが,Wolfram言語のようなシンボル式の処理システムでは,条件によってはTrueにも,Falseにもならないことがある.したがって,例えば,条件x==yでは,xyの両方の数値的な値が判明しない限りTrueFalseの判定は付かない.
この場合,条件のTrueFalseの判定は付かない.このため,If式は評価されない:
If式に第4の引数を特別に加え,TrueFalseの判定が付かないときのデフォルト値を提供する:
If[test,then,else,unknown]
testTrueでもFalseでもないときに使う式を含むIfの形
TrueQ[expr]
exprTrueのときはTrueを返し,それ以外のときはFalseを返す
lhs===rhs
あるいは
SameQ[lhs,rhs]
左辺 lhs と右辺 rhs が等しいときはTrueを返し,そうでなければFalseを返す
lhs=!=rhs
あるいは
UnsameQ[lhs,rhs]
左辺 lhs と右辺 rhs が等しくないときはTrueを返し,そうでなければFalse を返す
MatchQ[expr,form]
expr がパターン form にマッチするときはTrueを返し,そうでなければをFalse返す
シンボル式による条件を扱うための関数
この式はシンボル的な代数式としてそのまま残される:
TrueQ[expr]において expr が明らかにTrueにならなければ,exprFalseであるものとみなされる:
==と違い,===では,両辺が明示的に等しいかどうかが判定される.この場合は等しくない:
lhs===rhslhs==rhs の間には次の大きな相違点がある.前者は常にTrueFalseを返すが,後者はシンボル形式のまま入力を残すことがある(「方程式」を参照).===は式の構造を判定したいときによく使われ,==は数学的な等しさを判定したいときに使う.パターンマッチを行うWolfram言語の制御部では,実効的な===の機能が使われ,ある式が別の式に記述通りにマッチするかどうかが判断される.
===を使い両辺の構造が同じか判定する:
この場合,演算子==はあまり役に立つ結果を出してくれない:
条件子の設定において,test1&&test2&&のような組合せ条件を使う必要がよくある.この組合せ条件で重要な点は,条件項目 testiFalseとなるものがひとつでもあると判定結果はFalseになる,という点である.Wolfram言語は,条件項目 testi を1つずつ評価し,testi のいずれかがもFalseになると,その時点で判定を打ち切る.
expr1&&expr2&&expr3
条件式 expri を1つずつ評価していき,ひとつでもFalseになれば評価を打ち切る
expr1||expr2||expr3
条件式 expri を1つずつ評価していき,ひとつでもTrueになれば打ち切る
論理関係式の評価手順
2つの条件からなる関数を定義する:
この場合,両条件が評価される:
最初の条件はFalseになるので,2番目の条件は判定されない.仮に判定したなら,1/0のゼロ割エラーが発生してしまう:
Wolfram言語では,先の条件が満たされなければ後の条件は満足されないとする複数条件を並べた論理式を構築することができる.同様な処理手順がC言語のような言語でも見られるが,うまく利用すると各種プログラムを構築する上で便利になる.
ループと制御構造体
Wolfram言語プログラムの実行とは,一連のWolfram言語式の評価を意味する.単純なプログラムでは,式はセミコロンで区切られ,1つずつ評価されることが多い.しかし,多くの場合は,式を繰り返し評価する必要があり,ある種の「ループ」を使う必要が出てくる.
Do[expr,{i,imax}]
反復変数 i を刻み幅11から imax に増加させ,式 expr を繰り返し評価する
Do[expr,{i,imin,imax,di}]
反復変数 i を刻み幅 diimin から imax に増加させ,式 expr を繰り返し評価する
Do[expr,{i,list}]
ilist からの値を取って expr を評価する
Do[expr,{n}]
exprn 回評価する
簡単なループ構成体
i1から4に変化させたときのPrint[i^2]を評価する:
k2から6まで刻み幅2で変化させるループを使い,tに対する割当てを行う:
関数Doで使われる反復条件はTableSum等の関数と同様に指定する.後者の関数でできたように,一連の反復指定を与えることで,Do式でも複数レベルでネストされたループを構成することができる.
ループを作り,i1から4まで振り,各ij1からi-1までふる:
場合によっては,反復変数の値は変えずに,単に特定の操作を繰り返したい.Tableや他の反復関数を使ってもこの種の反復操作は行えるが,Do式でも行うことができる.
t=1/(1+t)による割当てを3回繰り返す:
Do式の内部に手続きを入れることもできる:
Nest[f,expr,n]
関数 f を式 exprn 回適用する
FixedPoint[f,expr]
関数 f を繰り返して式 expr に適用し,結果の式が変化しなくなる時点で停止する
NestWhile[f,expr,test]
結果の式に test を適用したものがTrueを返さなくなるまで,式 expr に関数 f を繰り返して適用する
関数の繰返し適用
Doを使えば,反復変数をいろいろな値に振り特定の式を評価することで一連の操作を繰り返すことが可能である.それでも,「関数を反復的に適用する」に示したような関数型プログラミングの構成体を使うと,分かりやすく,また,効率的なプログラムを記述することができる.例えば,関数Nest[f,x,n]を使えば関数を繰り返し,再帰的に式に適用する,ということが可能になる.
fを3回ネストさせる:
純関数をネストさせ,前述のDoの例で作った式と同じ計算をする式を作る:
このように,Nestは,関数を指定した回数だけ繰り返し適用するのに使う.それでは,関数を繰り返し適用させ,結果が変化しなくなったら自動的に停止させる,という処理を行いたい場合はどうするのか.そのときは,関数FixedPoint[f,x]を使い定常点の探索を行うようにすればよい.
FixedPointは,結果が変わらなくなるまで関数を繰り返し適用する:
関数FixedPointを使うことで,カーネルの評価プロセスそのものや,expr//.rules 等の関数操作をまねることができる.FixedPointでは,2回の逐次結果に変化がなくなるまで関数の適用が続けられる.一方NestWhileでは,任意の関数がTrueでなくなるまで続けられる.
Catch[expr]
Throw[value]が現れるまで式 expr を評価し続け,現れた時点で値 value を返す
Catch[expr,form]
tagform にマッチするThrow[value,tag]が現れるまで式 expr を評価し続け,現れた時点で値を返す
Catch[expr,form,f]
value の代りに f[value,tag]を返す
評価処理の非局所的な制御
評価中にThrowが現れるとCatchの処理は停止され,そのときのiの値が返される:
ThrowCatchを組み合せて使うと,Wolfram言語の評価プロセスを監視することができるため便利である.評価を監視したい式には,Catchを作用させておく.評価処理中にThrowが現れると,その時点で進行中の処理は停止し,それに与えられている引数の現行値が返される.
Scanを作用させ,Print関数をリストの各値に逐次に適用する.処理終了後,空文字(Null)が返される:
Throwが現れた時点で,Scan処理は停止する.Catchの戻り値として,Throwの引数の持つ現行値が返される:
Mapを使っても同じ結果を得ることができる.ただし,Mapだと,評価中にThrowが現れないときはリストが返されることになる:
ThrowCatchを使えば,関数的なプログラミング構成体における操作処理を特定条件が満たされるまで続行させ,満たされなくなった時点で中断させることができる.Throwで評価を中断すると,得られる結果は途中結果である.途中結果なので,式は,仮に最後まで評価を続けたときの式とは構成上多少違ってくることに注意してほしい.
関数を繰り返し適用し,リストを生成する:
Throwがないため,上の式と同じ結果が得られる:
今度は,NestListの評価が中断され,1つあるThrowの引数が返される:
ThrowCatchはともに大域的な働き方をする.Throwどこに,またどのように配されても,一度それが現れると,評価処理は必ず中断され,制御は直接Catchに戻される.
Throwによりfの評価が中断され,Catchの戻り値としてaが出力される.fは無視される:
関数を定義する.この関数は,引数に10より大きい値を入力した場合,Throwを発生させる:
Throwは起らない:
この場合,gの評価中に起るThrowにより,制御はCatchに戻される:
短いプログラムでは,Throw[value]Catch[expr]の最も単純な書式を使うだけでよい.しかし,構成部分が増えプログラムが長くなると,Throw[value,tag]Catch[expr,form]の書式を使った方がよいだろう.タグ tag と形 form を局所的なものとしておくことで,ThrowCatchがプログラムの特定部分だけで機能するようにすることができる.
タグがあるため,Throwにより投げ出される値は内側のCatchでキャッチされる:
今度は,外側のCatchによりキャッチされる:
パターンでCatchの認識するタグを指定してもよい:
こうすればタグaを完全に局所的なものとして維持できる:
ここでの注意点は,Throwで使うタグは定数である必要はない.通常,それはどんな式であっても構わない.
スローのタグの値が4より小さければ内側のCatchがキャッチし,Do式の処理が続行する.4以上になったら,外側のCatchで止められる:
Throw[value,tag]Catch[expr,form]の書式を使った場合,Catchの返してくる値はThrowに指定した第1引数 value になる.また,Catchの書式をCatch[expr,form,f]にすると,f[value,tag]が返される.
fThrowの値とタグに適用される:
Throwがなければ,fは決して使われない:
While[test,body]
条件式 test を判定し結果がTrueならば,式 body を評価する.条件式が真でなくなるまで式の評価を繰り返す
For[start,test,incr,body]
start を評価し,条件式 test が真でなくなるまで,式 body と式 incr の評価を繰り返す
一般的なループ構成体
構造化アプローチを取るなら,DoNestFixedPoint等の関数を使いループを構築するとよい.また,ThrowCatchを併せて使えば,ループを監視,制御することも可能である.一方,構造にとらわれないループを作りたい場合は,関数WhileForを使うとよい.任意条件下で繰返し処理を停止できるループを構築することができる.
Whileループは,条件が満たされなくなるまで実行される:
関数WhileForは,C言語のwhileforに構文上似ている.ただし,違いもある.例えば,Forループのコンマとセミコロンの使い方はC言語と逆である.
Forループの簡単な例を見てみる.i++iの値を1増加させる:
少し複雑なForループを作ってみよう.実行させると,ループは,条件式i^2<10が満たされなくなった時点で終了する:
WhileForのループにおいて,まず評価されるのは条件式であり,その次に,本体の式が評価される.つまり,Trueにならなければその時点でWhileおよびForループは終了してしまう.このため,本体の式が評価されるのは条件式がTrueになるときだけである.
ループの条件式はすぐに偽となるので,本体の式は決して評価されない:
WhileForによるループ,または,その他の手続きにおいて,式の評価は特定の手順に従って行われる.この評価手順をWolfram言語プログラムの実行における制御フローと呼ぶ.
一般に,制御フローはなるべく単純なものがよい.制御フローが,プログラム実行中に生成される特定値に依存すればするほど,プログラムの構造や動作が分かりづらいものになってしまう.
関数的な構成体では,制御フローが非常に単純である.WhileForのループでは,制御フローがプログラム実行時の条件式の値に左右されてしまうので,ループが複雑な構成になりがちである.とは言え,ループであっても,制御フローは,普通その本体で与えられた式の値に依存するようなことはない.
それでも,場合によっては,本体の式の値に依存した形でフローを変えてやらなければならない.これを行う方法として,まず,関数的なプログラミングの考え方に適合した手法,つまり,関数ThrowCatchを使う方法が考えられる.さらに,Wolfram言語にはC言語で提供される制御フローの操作用関数に相当した各種の関数が用意されているので,それらを利用するのも便利である.
Break[]
最も近いレベルにあるループから退避する
Continue[]
現行ループの次のステップに進む
Return[expr]
関数から値 expr を返す
Goto[name]
現行手続きにあるLabel[name]位置にジャンプする
Throw[value]
制御を戻し,Catchの戻り値として値 value を返す(非局所的なリターン)
フローの制御関数
t19を超え,制御がBreak[]に達した時点でループが停止する:
k<3のときは,Continue[]に制御が行き,t+=2の計算は実行されずにループが続行される:
Return[expr]を使い,戻り値を指定することも可能である.ネストされた関数式から一挙に退避できることから,Throwを非局所的なリターン機能と考えることもできる.エラー処理に利用すると便利である.
Returnを使った例を見てみる.必ずしもReturnを使わなくてもこの手続きを記述することができる:
引数が5より大きいので,手続きでは最初のReturnが使われる:
引数が負の数のときはエラー値errorを返すように関数を定義する:
実行してもThrowに制御は移らない:
今度は,負になるからThrowが生成され,Catchの戻り値はerrorになる:
Continue[]Break[]の関数はループの始点もしくは終点に制御を移すために使う.場合によっては,ループの外にある手続き内の任意の位置に制御を移したい.そのようなときは,転送先を関数Labelであらかじめラベル指定をしてから関数Gotoを使い実際に制御を移す.
q6を超えるまでループが続行する:
注意してほしいが,Gotoを使うには同じ手続き内にLabel指定がなければならない.ただし,Goto指定を使うとフロー構造が入り組んだものになり,プログラムがどう機能しているのか分かりづらくなるので使用には注意が必要である.
評価中に式を集める
多くの場合は入力された式の評価の最終結果だけに興味があるが,評価の途中で生成された式を取り出したいこともある.SowReapを使うとこれが可能になる.
Sow[val]
最も近くに囲っているReapに値 val を播く
Reap[expr]
expr を評価し,Sowによって播かれた値のリストも一緒に返す
SowReapを使う
ここでは出力に最終結果しか入っていない:
次の場合は中間の2つの結果もまた含まれている:
これは偶数項をすべて集めて総和を計算する:
SowReapThrowCatchのように計算のどこででも使うことができる.
Sowできる関数を定義する:
これは1/2より小さな場合をすべて刈り取って関数をネストさせる:
Sow[val,tag]
刈り取るときを示したタグを付けて val を播く
Sow[val,{tag1,tag2,}]
各タグ tagival を播く
Reap[expr,form]
タグが form にマッチするすべての値を刈り取る
Reap[expr,{form1,form2,}]
formi ごとに別々のリストを作る
Reap[expr,{form1,},f]
各タグと値のリストに f を適用する
タグ播きと刈り取り
これはのタグxを付けて播かれた値だけを刈り取る:
ここでは1がタグxを付けて2度播かれている:
異なるタグを付けて播かれた値は異なるサブリストに入れられる:
これは播かれたタグの形式別のサブリストを作る:
個々のタグと値のリストにfを適用する:
このタグを計算の一部にすることができる:
評価処理のトレース
Wolframシステムの標準評価処理は,ユーザの式の入力を取り込み,式を評価し,評価が完了したら結果を返す,という過程からなっている.本節では,最終的な評価結果だけでなく,中間過程でも評価がどう進行するのかを見ていく.そうすることで,評価処理の理解の助けにもなるだろう.
Trace[expr]
expr の評価で生成されるすべての中間結果の式を列挙する
Trace[expr,form]
パターン form にマッチする中間結果の式だけを列挙する
式評価のトレース
トレースさせると,式1+1が計算され,直ちに2が出力されてくる:
足し算の前に2^3が評価されている:
ベキ項が評価されると,その結果はサブリストの形で出力される:
Trace[expr]を使えば,式 expr の評価過程において中間結果として生成されるすべての中間式を列挙させることができる.ただし,もとの式が簡単なものでないと,非常に長いリストが出力されてしまい,Traceを行っても何のことか分からなくなってしまう.
複雑な式をTraceする際は,もう1つの書式,Trace[expr,form]を使った方がよい.パターン form にマッチする中間式だけを選んで出力することができる.
階乗を計算するための帰納的関係を定義しておく:
階乗関数fac[3]をトレースする.中間式をすべて出力したので,結果は非常に複雑なものになる:
次に,パターンを加えてfac[n_]の形に準拠する中間式だけを出力する:
Traceで指定するパターンは何でもよい.例えば,これでもよい:
Trace[expr,form]を使うと,式 expr の評価過程で生成されるすべての中間式は一旦判定され,パターン form に合った式だけが抽出される.
例えば,関数facを作ったとして,その関数が呼び出されているときだけトレースを実行したければ,パターンをfac[n_]にしTraceに与えておく.また,パターンをf[n_,2]としておけば,引数を伴った特殊構成の関数式だけを抽出できるようになる.
Wolframシステムによる典型的なプログラムでは,fac[n]等の関数呼出しの式だけでなく,変数の割当てや,制御構造体等のいろいろな要素が登場する.これらの要素はすべて一般的な「式」とみなされるので,Traceに適切なパターン指定さえしておけば,どんな要素でも個別に抽出することができる.例えば,パターンをk=_としておけば,変数kに関するどんな割当て式でも抽出できるようになる.
kに関して生成される割当て式をすべて表示させる:
expr の評価において,そのどの過程で生成された中間式でもTrace[expr,form]を使えば限定抽出することができる.また,抽出すべき中間式は式 expr から直接得られるものでなくてもよい.つまり,expr の評価処理の一部として関数が呼び出され,その結果生成されるような式であってもよい.
トレースする関数を定義しておく:
関数hの評価中に生成される式を追跡する:
以上のように,Traceを使い中間式を追跡することができるが,追跡の対象になる式はユーザ定義の関数だけでなく,一部の組込み関数でもよい.ただし,注意点として組込み関数の場合,Wolframシステムのバージョンによってはアルゴリズムの記述や最適化処理が詳細な点で違うため,評価過程の順序が違ってくる場合がある.
Trace[expr,f[___]]
式の評価における関数 f の呼出しをすべて表示する
Trace[expr,i=_]
変数 i への割当て式だけを表示する
Trace[expr,_=_]
すべての割当て式を表示する
Trace[expr,Message[___]]
評価中に生成されるメッセージを表示する
Traceの限定
関数Traceにより出力される式のリストは,Wolframシステムの計算処理の履歴である.中間式は計算処理で実行された順序通りに並べられる.また,Traceの結果のリストは,ほとんどの場合,ネストされた形で出力されるが,これは実際に行われた計算の「構造」を表している.
基本的に,Traceの結果であるリストの持つサブリストは,特定のWolframシステム式の評価チェーン(鎖)を表す.また,サブリストの要素はチェーンに相当し,1つの式が取るいろいろな変換形を表している.ただし,通常は,1つの式を評価するには他の式も評価しなければならないので,実際のTraceでは,従属評価の結果もサブリストに含まれることになる.
割当ての列を定義する:
トレースすると,a[i]の変換処理の様子が評価チェーンとして現れる:
y+x+yの簡約化処理で生成される式の形が評価チェーンの連鎖要素として表示される:
各関fの各引数に数評価チェーンがあり,中間結果はサブリストの形で表示される:
評価チェーンは,式の要素別にサブリストで表示される:
ネストされた式をトレースすると,トレース結果のリストもネストされた形で返される:
あるWolframシステム式の評価において従属評価を必要とする状況は基本的に2つある.1つは,式が複数の副次的な式からなる場合で,そのとき各副次式は別々に評価される必要がある.もう1つは,式の評価において規則が適用され,その適用時に別の関連式をも評価する必要がある場合である.どちらの場合も,従属評価の結果は,Traceにより返される構造体におけるサブリストとして表される.
従属的評価は,fgの引数評価から派生する:
条件付き関数を定義する:
fe[6]の評価は,条件に関連した従属評価を伴う:
再帰的処理を踏む関数の評価をトレースさせると,結果はネストされたリスト形で得られる.これは,評価の対象となる関数は1つでも,反復過程の途中で新たに生成される中間式が評価前の中間式に従属する形で生成されるからである.
例えば,fac[n_]:=n fac[n-1]と定義を作り,fac[6]を評価させたとすると,最初の評価では6 fac[5]が生成される.ここで,fac[5]は従属評価から派生した式である.
facが繰り返し評価され,中間結果がネストされたサブリストで表示される:
fp[n-1]を直接fp[n]の値とする定義を作っておく:
fp[n]の形の式は生成されることはないので,トレース結果にサブリストはない:
これは,Fibonacci数の再帰的定義である:
最終条件を与える:
fib[5]の再帰評価における全ステップが表示される:
式の評価過程で生成される中間式は,変換規則が適用された結果発生する.「異なるシンボルへの定義式の関連付け」で説明したように,定義済みの変換規則はすべて特定のシンボルに直接,もしくは,タグを介して間接的に関連付けられる.Trace[expr,f]を使い,式 expr をトレースさせることで,シンボル f に関連付けられている変換規則がどう適用されるかを調べることができる.この場合,Traceは,規則の適用前の中間式だけでなく,適用後の結果である中間式も返す.
Trace[expr,form]を使い式 expr をトレースさせると,パターン form にマッチした評価直前の中間式,または,そのパターンにマッチした,使われた規則に関連付けられたタグがすべて抽出される.
Trace[expr,f]
式の評価において,f に関連した変換規則を使った評価ステップをすべて表示する
Trace[expr,f|g]
f もしくは g に関連した評価ステップをすべて表示する
特定タグに関連した評価ステップのトレース
fac[_]にマッチする中間式だけを表示させる:
シンボルfacに関連付けられている変換規則を使う評価ステップをすべて表示させる:
log関数の規則を定義する:
log[abcd]の評価過程をトレースさせ,logに関連した変換規則をすべて表示させる:
Trace[expr,form,TraceOn->oform]
中間式が oform にマッチした要素を持つときのステップだけをトレースする
Trace[expr,form,TraceOff->oform]
中間式が oform にマッチした要素を持たないときのステップをトレースする
式の要素で範囲限定したトレース
Trace[expr,form]を使うと,パターン form に合った中間式だけが抽出されるが,トレース範囲としては,式 expr のすべての評価ステップが対象になる.場合によっては,expr の評価における範囲を限定して,限られたステップ中だけでトレースを行いたい.
これを行うには,TraceOn->oform のオプション設定を使う.パターン oform にマッチした要素を持つ中間式のステップに限ってトレースが実行される.反対に,あるステップだけをトレースから排除したければ,TraceOff->oform とすることで,oform を持つ中間式を除いたステップだけをトレースすることができる.
評価過程の全ステップをトレースする:
fac関連のステップだけをトレースする:
fac関連以外のステップをトレースする:
Trace[expr,lhs->rhs]
expr の評価において,左辺 lhs にマッチするすべての中間式を抽出し,右辺 rhs に変換し表示する
評価中に生成される式への規則の適用
Traceが返すものをfib[5]の評価で使われるfibの引数だけに限定する:
Trace関数の強力な面として,この関数により返されるリストは,基本的に,他の関数で操作できる標準的なWolframシステム式である.ただし,Traceによりリストの中の式にはHoldFormがかけられているので,そのままでは入力としては使うことができない.また,注意点として,HoldFormは内部で有効にはなっているが,表示が標準出力表記だと,HoldFormの記述自体は表示に現れない.
評価の全ステップをトレースさせる:
トレースされた中間式にはHoldFormがかけられているため,そのままでは入力には使えない:
標準の出力表記だと,どのリスト要素がTraceの評価結果のものか,または,評価前の式なのかは分かりづらい:
入力形で見るとはっきりする:
Traceで変換規則を使うと,規則の適用結果がまず評価され,それから,HoldFormが適用される:
複雑な計算では,Traceで得られるリストもまた複雑な構成になりがちである.Trace[expr,form]を使えば,パターン form に合った中間式だけをリストの要素として抽出することができる.しかし,どんなパターンを使おうが,最終的に得られるリストのネスト構造は変わらない.
fib[3]をトレースさせ,fib[_]のパターンに合った中間式を列挙させる:
今度は,fib[1]だけを表示する.リストのネスト構成はfib[_]のリストと同じであることに注目:
TraceDepth->n のオプション設定をTraceの指定に加えると,トレース可能なネストレベルの上限はレベル n に設定される.そうすることで,長くなる解法において,細かい計算をスキップし,主要ステップだけを追うことができるため,トレースの効率を上げることができる.さらに,オプションTraceDepthTraceOffを組み合せて使えば,追跡したいステップをさらに絞れるのでTrace作業をさらに速くすることができる.
ネストレベルが2以下のステップだけをトレースさせる:
Trace[expr,form,TraceDepth->n]
expr のトレースにおいて,ネストレベル n 以下の評価ステップを列挙する
ネストレベルを限定したトレース
Trace[expr,form]を使えば,式 expr の評価で生成される中間式のうち form に合ったものだけを抽出することができる.ただし,この方法だと式だけが列挙され,式の評価から求まる値は通常出力されない.TraceForward->Trueのオプション指定をTraceに加えておけば,評価結果も見ることができる.
fac[_]にマッチする中間式だけでなく,これらの式の評価結果も表示される:
Trace[expr,form]で抽出されるほとんどの式は評価チェーンの中間ステップで生成される式である.TraceForward->Trueの指定をした場合は,評価チェーンの最終ステップで得られる式もTrace結果に入れられる.また,指定をTraceForward->Allとすると,form にマッチした式が評価された後に評価チェーンにおいて生成されるすべての式が抽出される.
TraceForward->Allと指定したので,fac[_]にマッチする式の後に生成される評価チェーンの全要素が抽出される:
TraceForwardを有効にしておくと,特定の形の式がどう評価されるかを追跡することができるようになる.場合によっては,式がこれからどうなるかより,どうしてこの式になったかを知りたい.そのときは,オプションTraceBackwardを有効にしておく.すると,TraceBackwardにより特定の式が生成される前に評価チェーンがどうだったかを観察することができるようになる.
fac[10]をトレースする.120の値はfac[5]の評価によることが分かる:
120の値に関連した評価チェーンの全容を追跡する:
TraceForwardTraceBackwardを使い分けると,評価チェーンを昇り降りして評価を追跡することができる.場合によっては,特定の評価チェーンから派生する子チェーンまで追跡したい.これを行うには,TraceAboveを指定する.例えば,TraceAbove->Trueと設定しておけば,関連するすべての子チェーンの最初と最後における中間式がTraceに含まれるようになる.また,TraceAbove->Allとしておけば,子チェーンの全ステップにおける式をTraceすることができるようになる.
子チェーンを含む全評価チェーンの最初と最後において,数120を含む式を抽出させる:
fib[5]をトレースする.fib[2]を生成するチェーンにおける全ステップを抽出させる:
Trace[expr,form,opts]
オプション設定に従って,式 expr の評価をトレースする
TraceForward->True
form を含む評価チェーンの最後の式をリストに加える
TraceForward->All
評価チェーンにおいて form 登場以降の全ステップで生成される式をリストに加える
TraceBackward->True
form を含む評価チェーンにおいて最初の式をリストに加える
TraceBackward->All
評価チェーンにおいて form 登場以前の全ステップで生成される式をリストに加える
TraceAbove->True
form を含む子チェーンに通じる全評価チェーンにおいて最初と最後の式をリストに加える
TraceAbove->All
form を含む子チェーンに通じる全評価チェーンにおいて全ステップの式をリストに加える
トレースのリストに抽出されるステップを指定するためのオプション設定
Trace[expr,]の基本動作は次の通りである.まず,式 expr の評価の過程で生じる中間式が抽出される.次に,中間式がパターンや設定条件に見合うものかが判断され,そうならば,リスト出力用に内部保持される.このとき,式の持つ関数の引数が評価された後にできた中間式だけがTraceによる抽出の対象になる.また,TraceOriginal->Trueの設定が有効なら,引数が評価される前の式の形もTraceの対象にされる.
このトレースでは,引数評価の前と後で生成されるfac[_]にマッチした式が抽出される:
Traceで得られるリストには,通常,非自明的な評価チェーンのステップで生成された式だけが出力される.つまり,評価後と評価前で同じ式が得られるような評価ステップは除外される.ただし,TraceOriginal->Trueの指定が有効なときはその限りでなく,Traceでは自明的かどうかにかかわらず評価過程の全ステップが出力される.
Traceが有効である場合,自明的な評価チェーンも含むすべての式がトレースさせる:
オプション
デフォルト値
TraceForwardFalse
form 以降の評価チェーンの式を表示するかどうかを指定
TraceBackwardFalse
form 以前の評価チェーンの式を表示するかどうかを指定
TraceAboveFalse
form を含む子チェーンを生みだす評価チェーンを表示するかどうかを指定
TraceOriginalFalse
頭部と引数の評価を実行する前の式を表示するかどうかを指定
Traceで使われる補足的なオプション
実行中のプログラムにTraceを使うとき,プログラム中の局所変数をどのように扱うか,という問題が起る.「モジュールの動作の仕方」で説明するように,Module等の構成体ではスコープを限定した局所変数が使えるようになっている.従って,プログラム中のもとのコードで変数xを呼んでも,こうして設けられた局所変数にはx$nnn の形式で参照名が付けられる.
そこで,form に現れるシンボル x が,expr の実行で発生する形式 x$nnn の名前を持つすべてのシンボルにデフォルトでマッチするように,Trace[expr,form]が設定される.つまり,Trace[expr,x=_]と入力すると,もとのプログラムにおいて変数が x の名前で参照されている限り,それが大域定義のものでも局所定義のものでもトレースの対象にされる.
Trace[expr,form,MatchLocalNames->False]
参照名が form で始まる局所変数をトレースリストから除外する
トレースからの局所変数の除外
場合によっては,名前を x とする大域変数だけをトレースの対象としたい.局所変数の記述名がxであってもそれは除外したい.そうするには,MatchLocalNames->Falseのオプション設定を加えておく.
x$nnn 形式の名前を持つ変数すべてに関連した割当て評価をトレースする:
今度は,大域変数 x に限ってトレースする:
関数Traceを使うと,まず,式の計算処理が完全に実行され,次に,計算処理の履歴を示す中間式のリストが表示される.特に,計算が長くなる場合は,計算処理の進行に並行した形で随時トレース結果を観測することができると便利である.関数TracePrintを使えばそれが可能になる.この関数は機能上Traceと同じだが,表示の仕方が違う.つまり,Traceのように抽出したすべての式を最後にリストで一括表示するのではなく,中間式が生成されるたびに式の表示を逐一行う.
fib[3]の評価で生成される式を生成時に逐一表示させる:
TracePrintにより返される一連の式は,Traceが返すリスト要素の式と同じである.TracePrintの式の表示において,画面左側のインデント幅は式のネストレベルを示している(これは,Traceリストにおけるサブリストのレベルに相当する).TracePrintの動作設定には,Traceの設定で使ったオプションTraceOnTraceOffTraceForwardを使うことができる.ただし,TracePrintは順次,式を出力していくので,TraceBackwardの指定は行うことができない.また,TraceOriginalの設定値は実効的に常にTrueとされる.
Trace[expr,]
expr の評価をトレースし,抽出した中間式は評価終了時にリスト形式で一括表示する
TracePrint[expr,]
expr の評価をトレースし,抽出した中間式は評価中に逐次表示する
TraceDialog[expr,]
expr の評価をトレースし,指定した中間式が生成されるとダイアログを開始する
TraceScan[f,expr,]
expr の評価をトレースし,HoldForm適用の中間式に f を適用する
評価処理をトレースするための関数
fac[10]の評価で,fac[5]が生成されるとダイアログモードが開始する:
ダイアログが始まると,スタックを参照し,現在位置を確認する:
ダイアログを終了する.戻り値としてfac[10]の最終評価結果が得られる:
TraceDialogを使うと,計算処理を途中で一旦停止させ,停止時の演算環境をもとにWolframシステムと対話ができるようになる.評価で一時的に使われる変数の現行値を調べたり,別の値に再設定したりすることができる.ただし,微妙な点で(特に,パターンやモジュール変数等に関連した点で),通常の割当てとは違うので注意が必要である.
TraceDialogのトレース中に一連の式が生成されると関数Dialogが呼び出される(Dialogの機能詳細に関しては「ダイアログ」を参照のこと).Dialogを呼び出すということは,2次的な計算セッションを開始させるということで,Wolframシステムとの対話は別の入出力行が使われ進行する.
expr のトレースで抽出される中間式に任意な関数 f を適用したければ,TraceScan[f,expr,]の書式を使う.実際に f の適用がある式にはHoldFormが適用され,評価されないよう式が保護される.
TraceScan[f,expr,]では,中間式が評価される前に関数 f が適用される.ここで,TraceScan[f,expr,patt,fp]を使うと,評価の前には f が適用され,評価の後には fp が適用されるようになる.
評価処理用スタック
Wolframシステム内部には「評価スタック」と呼ばれる現在評価中の式を記録しておく場所が用意されている.関数Stackを使うと,そのスタックの記録内容を参照できる.計算を一時停止させたとき等にStackを実行し計算の状態がどうなっているか調べることができる.
評価スタックにおける式は,最も最近評価された式がスタックの最後の式となるように並べられる.すでに評価が進行している式は,スタックの先頭寄りに置かれる.
例えば,式 f[g[x]]の評価において,現時点で x が評価されているとする.スタックを参照すると{f[g[x]],g[x],x}の順序で評価が進んでいる旨が表示される.
Stack[_]は,それが呼び出されるときに評価中の式を返す.その中にはPrint関数も含まれる:
Stack[]は,呼び出されるときに進行中の評価に関連したタグを返す:
評価スタックは,特定の計算においてWolframシステムの現在位置に到達するまでに,どの関数がどの他の関数を呼び出したかを表示してくれるものととらえることができる.こうして表示される式の列は,オプションTraceAboveTrueとしたときのTrace関数により返される一連のネストしたリストの第1要素に相当する.
Stack[]
評価時点で進行中の評価処理に関連したタグを列挙する
Stack[_]
評価処理中の式をすべて列挙する
Stack[form]
パターン form にマッチする式だけを列挙する
評価スタックの参照
通常の入出力セッションで直接Stackを使うことはあまりないだろう.通常は,計算を一時保留にしてからダイアログモードにまず入り,その後で二次的セッションからStackを実行する(ダイアログに関しては「ダイアログ」を参照のこと).
階乗のための標準的な帰納的関数を定義しておく:
fac[10]の評価をトレースし,fac[4]が現れた時点でダイアログモードに入る:
ダイアログが開始したので,まず,スタックを調べ,どんなオブジェクトが評価中かを見てみる:
ダイアログを終了する:
最も単純なのは,評価スタックが評価中のすべての式を記録できるように設定されている場合である.しかし,場合によっては,網羅しない方がよいときもある.例えば,Print[Stack[]]が実行されると,Printが常にスタックの最後の関数になってしまうため不具合が生じる.
StackInhibitを使えば,そのようなことが起らないようにできる.書式StackInhibit[expr]を使うと,式 expr の評価はされるが,評価で発生する関数呼出しはスタックに登録されないようになる.
StackInhibitを使いPrintがスタックに記録されないようにしておく:
TraceDialog等の関数を使いダイアログを開始させると,StackInhibitはその都度自動的に呼び出される.このため,ダイアログの中で使われた関数はStackでは表示されない.
StackInhibit[expr]
expr は評価するが,スタックへの記録を禁止する
StackBegin[expr]
expr を評価し,新規スタックを使う
StackComplete[expr]
expr を評価し,評価チェーンで生成される中間式もスタックに記録する
評価スタックの制御
StackInhibitStackBeginを使い評価プロセスのどの部分をスタックに記録するか指定できる.StackBegin[expr]は,式 expr が評価される前に新たなスタックを用意する.つまり,スタックには式 expr の評価で使われたもの以外は記録されない.StackBeginの前に使われた関数等は入らない.TraceDialog[expr,]等のダイアログ用関数では,まずStackBeginが呼び出され,それから式 expr が評価される.このため,スタックを参照することで,式 expr がどのように評価されるかは知ることができるが,TraceDialog自体がどう呼ばれたかは分からない.
StackBegin[expr]は,expr の評価に新たなスタックを使う:
Stackは現在評価中の式だけを表示する.このため,最も最近の式の形が表示される.しかし,場合によっては,過去に取った式の形を見ることができると好都合である.これを行うには,StackCompleteを使う.
StackComplete[expr]を使うと,評価中の各式から派生した評価チェーンの逐一をスタックに記録することができる.この機能は,TraceTraceAbove->TrueTraceBackward->Allのオプション設定をしたときに得られる機能に相当する.
無限評価の防止
式の評価でWolfram言語の取る基本操作は,式に変化が見られなくなるまで別の変換規則を適用していくというものである.このため,x=x+1のような割当て式を入力すると,評価が永久に終らないことになりかねない.そこで,有限回のステップが過ぎると(再帰回数の上限は大域変数$RecursionLimitで決定される)Wolfram言語は自動的に処理を強制停止するようになっている.もちろん,ユーザは手動でいつでも停止させることができる.
わざと無限ループになる式を入力する.$RecursionLimitの回数以上の評価ステップが繰り返されると処理が停止してしまう:
評価自体は終っていないので,保留とされた結果が返される.特別にReleaseHoldを呼び出すことで,中断した評価を続行させることも可能である:
$RecursionLimit
評価スタックの深さの上限
$IterationLimit
ループ等の評価チェーンにおける最大繰返し数
無限評価を防止するための大域変数
わざと循環する定義を作る.この場合,評価は$IterationLimitのループ上限で停止する:
大域変数$RecursionLimit$IterationLimitは,2つの基本的な繰返し方法を制御し,無限の評価処理が起らないようにするためにある.$RecursionLimitは評価スタックの最大量を決定する.また,Traceのリスト出力におけるネストレベル上限にもこのパラメータが使われる.これに対して,$IterationLimitは1つの評価チェーンの許容する最大ステップ数を決定する.このパラメータは,Traceのリスト出力における単一サブリストの長さにも相当する.
$RecursionLimit$IterationLimit自体のデフォルト値はよくある計算負荷と平均的なコンピュータシステムの能力に合わせて設定してある.ユーザが上限パラメータを他の整数値に変更するのは構わないが,(デフォルトの下限より大きく)無限大Infinityより小さい整数値を指定する.特殊なシステムを使っていない限り,$RecursionLimit=Infinityとは設定してはいけない(「メモリ管理」を参照のこと).
$RecursionLimit$IterationLimit20に設定する:
20回反復して処理が停止する:
最終条件がないので,この定義を実行すると無限回反復してしまう:
20にしておいても,かなり大きなリストになってしまう:
反復定義をもう1つ行う:
今度は,複雑な式は何もできない.計算自体は$IterationLimitの上限で停止する:
注意点として,無限ループを行うとコンピュータのメモリが多量に消費されてしまう.反復処理の上限が$IterationLimitで決定されるような計算では,中間結果として生成される構造体が巨大になるというようなことはない.しかし,$RecursionLimitで上限が決まる計算では,そのようなことがあり得る.再帰的な処理で生成される構造体は$RecursionLimitの上限値に正比例して増大し,計算する式によっては,$RecursionLimitに指数関数的に比例して増えるものもある.
x=x+1のような式が循環してしまうことは明白である.しかし,再帰的関係が少し込み入ったものになると,いつ計算が終了するかを確実に判断することは難しい.これは,基本的な注意事項だが,変換規則を適用する前には,必ず式の右辺と左辺で記述内容が違っていることを確認する.そうすれば,少なくとも,同じ変換規則を同じ式に繰り返し適用してしまうような初歩的な間違いを防ぐことができる.
まぎらわしいのは,規則が/;の条件に依存する場合である(/;の条件は「パターン適用範囲の制限」を参照のこと).その条件が大域変数を参照しているときはさらにまぎらわしくなってしまう.式が変化しなくなりさえすれば,Wolfram言語にとって評価は終了したことになる.しかし,他の式の評価で大域変数の値が変わってしまい,評価結果が狂ってしまう可能性がある.このような状況を防ぐには,まず,大域変数を/;の条件では使わないようにする.それでも疑わしい場合は,Update[s]の書式を使い強制的に s を参照している式をすべて更新させる.また,Update[]と入力すれば,すべての式を無差別に更新させることができる.
処理の中断と放棄
「計算処理の一時停止」でキーボード操作による計算処理の一時停止の仕方を説明した.
場合によっては,停止操作をプログラムの中から直接行いたい.中断関数Interrupt[]を使えばそれができる.中断を行うと,「計算処理の一時停止」にあるように多くのシステムではメニューが表示されるので,そこから次に取る操作を対話的に選択することができる.
Interrupt[]
計算を途中で一時中断し保留状態にする
Abort[]
計算を途中で放棄する
CheckAbort[expr,failexpr]
expr を評価し結果を返す.評価が放棄された場合は failexpr を返す
AbortProtect[expr]
expr の評価が終了するまで放棄が実行されないようにする
計算処理の中断と放棄
関数Abort[]は,計算を一時中断し,中断メニューの放棄(abort)を選択することに等しい.
Abort[]は,プログラムで「緊急停止」をさせたい箇所に挿入される.ただし,通常の式ではReturnThrowを代りに使った方が,より制御性の高いプログラムを構築することができる.
Abortを使い途中で処理を終える.最初のPrintだけが実行される:
式の評価を放棄すると,その式に関するすべての評価結果は無効になり,戻り値としては$Abortedが得られる.
関数CheckAbortを使えば,放棄の操作を差し止めることができる.CheckAbort[expr,failexpr]の書式で関数を式 expr に適用しておくと,CheckAbortがあっても,実際に評価は放棄されず,代りに式 failexpr が出力される.ダイアログ用の関数Dialog等ではCheckAbortが使われ放棄関数による実際の放棄操作が行われないようになっている.
CheckAbortを作用させ,放棄操作をキャッチさせる.cが出力され値abortedが返されて評価が放棄されたことが分かる:
CheckAbortを使ったので,Abortに当たっても放棄されず,bが出力される:
洗練されたプログラムを構築する際には,放棄されては困る部分が出てくる.ダイアログによる対話的な放棄操作と,関数Abortを使った放棄操作はともに禁止にしておきたい.そのようなときは,AbortProtectを必要な部分に作用させておく.すると,放棄操作が起っても評価は続行され,評価終了時に,どんな放棄操作があったかが報告される.
AbortProtectを作用させ,Abortに当たっても評価を実行させる.評価完了時に放棄操作があったことが表示される:
CheckAbortで放棄されたことが検知されるが,処理自体は停止せず,次の式Printが実行される:
AbortProtectを適用した式でも,CheckAbortを式の中に入れておくと,放棄操作が自動検出され,式 failexpr が評価される.failexprAbort[]の記述がない限り,放棄操作は無効になる.
式のコンパイル
f[x_]:=x Sin[x]と定義したとする.右辺の式x Sin[x]は,xがどのような値をとっても適用可能な形で格納される.ユーザによりxの値が特定されると,値はx Sin[x]に代入され式の評価が行われる.特定されるxの値は何でもよい.数値でも,リストでも,代数式であってもよい.Wolfram言語の内部コードはすべてのオブジェクト型に対応するように作られている.
しかし,すべてのオブジェクトに対応しなければいけないため,処理時間は余計にかかる.そこで,xが例えば,機械精度の数値であることがあらかじめ分かっているなら,多くの処理ステップを踏む必要がなくなるため,潜在的に,x Sin[x]のような式をもっと速く評価することが可能になる.
式で参照される変数がすべて数(もしくは論理変数)とみなせるなら,関数Compileを使うことで式を高速なコードにコンパイルすることができる.Compile[{x1,x2,},expr]を使うと,式 expr はコンパイルされ,「コンパイル済み関数」を得ることができる.式を評価させるには,コンパイルされた関数に を与えその関数を実行する.
Compileの結果としてコンパイル済み関数CompiledFunctionと呼ばれるオブジェクトが生成される.そこには,実際のコンパイル済み関数の評価に必要な命令コード(インストラクション)が入っている.インストラクションは,使用されるコンピュータの機械語で書かれているので,高速な計算処理を実現することができる.
Compile[{x1,x2,},expr]
xi を数値的な変数とし,式 expr を計算するコンパイル済み関数を生成する
式のコンパイル
xが何であっても,x Sin[x]を計算することができる純関数を作り,それをfと呼ぶ:
x Sin[x]を評価してコンパイルした関数を作る:
ffcも同じ答を出すが,引数が数値なら,fcの方が計算が速い:
数値を使う式や論理演算式を繰り返し評価する必要があるときは,Compileを使うと効率が上がる.関数Compileを一度呼び出すだけで,通常のWolfram言語関数に比べ高速に処理してくれるコードが手に入る.
x Sin[x]のように,式が単純であればコンパイルしようがしまいが処理速度はあまり違わない.しかし,式が長くなると,最大で20倍程度まで処理速度を上げることができるため,コンパイルするメリットが大きい.
コンパイルして著しい違いが出るのは,単純な(数論的な)関数をたくさん使った式の場合である.複雑な計算,例えば,ベッセル関数(BesselK)や固有値の解析関数(Eigenvalues)を使った計算では,コンパイルしても処理速度はあまり向上しない.これは,計算時間の大半が,コンパイル処理に影響されないWolfram言語内部のアルゴリズムで費やされるからである.
ルジャンドル(Legendre)多項式の10次項を求めるコンパイル済み関数を作る.EvaluateによってWolfram言語はコンパイルする前に多項式を明示的に構築する:
引数を0.4としたときの10次の項を計算する:
組込み関数を使って同じことを計算させる:
数値的な関数であればコンパイルすることで計算速度を上げられる.しかし,それでも,できるだけ組込み関数を使うようにした方がよい.組込み関数は最適化してあるので,コンパイルされたユーザ定義の関数より速いことが多い.それに,引数の型が何であろうと同一関数で処理することができる.コンパイル済み関数だと,特定精度の数値的な引数にしか対応することができない.
また,組込み関数によっては自らが関数Compileを呼び出してコンパイルを行うものがある.例えば,数値積分の関数NIntegrateは,与えられた積分式を自動的にCompileを使う.同じように,プロット関数PlotPlot3Dはプロットする式をあらかじめCompileできるようになっている.Compileを利用する組込み関数にはコンパイル指定のオプションCompiledが用意されている.Compiled->Falseとしておけば,式のCompileを禁止にする.
Compile[{{x1,t1},{x2,t2},},expr]
変数 xi を型 ti の数とみなし,式 expr をコンパイルする
Compile[{{x1,t1,n1},{x2,t2,n2},},expr]
変数 xi を型 ti の数からなる ni 次元の配列とみなし,式 expr をコンパイルする
Compile[vars,expr,{{p1,pt1},}]
式の要素 pi を型 pti のオブジェクトとみなし,式 expr をコンパイルする
_Integer
機械精度の整数型
_Real
機械精度の近似実数型
_Complex
機械精度の近似複素数型
True|False
論理変数
コンパイルで有効な変数の型
Compileの機能がうまく機能するには,式にある各オブジェクトの型が確定していることが不可欠である.特に指定がなければ,すべての変数は近似実数とみなされる.
別途指定さえすれば,変数が整数や複素数でも,真偽二値(TrueFalse)の論理変数でも,さらに,数値配列的なものでもCompileをすることが可能である.変数の型を指定するにはパターンを使う.例えば,整数なら,_Integerとパターンを指定する.また,論理値の型なら,TrueFalseでなければならない論理変数を指定するためにTrue|Falseとパターンを指定する.
5i+jをコンパイルする.ijは整数であると指定しておく:
答は整数型で返ってくる:
整数型の行列についてリスト操作を行うための式をコンパイルする:
生成したコンパイルコードを使いリスト操作を実行する.結果は整数で返される:
Compileの扱える変数の型は,基本的に,コンピュータが機械語レベルで直接処理できる型なら何でもよい.つまり,機械精度の近似実数はCompileが使える.しかし,任意桁精度の実数はコンパイルできない.さらに,整数に関しては,機械精度の整数,例えば,以内の整数にCompileを使うことができる.
コンパイルする式が四則演算や論理演算だけのものなら,Compileは入力される変数の型から演算の各ステップで生成される値はどの型のものか自動判定できる.しかし,他の関数を参照したりすると,Compileは返される値がどんな型のものか判断がつかなくなってしまう.そのようなときは,Compileは特に指定がなければ,戻り値はすべて近似実数の型とみなす.ただし,特別にリストで型のパターンを与えておけば,式を任意の型のものとして解釈させることも可能である.
引数に整数を与えると,答も整数で返ってくる:
x^com[i]の式をコンパイルする.パターンcom[_]にマッチするものは整数であると指定しておく:
コンパイル済みの関数を使い計算してみる:
Compileとは,引数の型に応じて最適化された関数を生成することにある.実は,Compileは,それが生成する関数がどんな型の引数を取ろうが正常に機能するようになっている.引数が最適化の対象以外の型なら,コンパイルした式ではなく,標準形の式が呼び出され,引数の型に応じた計算が行えるようになっている.
変数の平方根を計算するコンパイル済み関数を作っておく:
引数が実数値なので,最適化済みコードが使われる:
未知数を与えると,もとの標準形の式が使われる:
Compileが的確にコンパイルするためには,与える引数の型だけでなく,実行時に生じるオブジェクトすべてについて型が判明していなければならない.オブジェクトによっては,型が実行時の引数の型に依存し特定できない.例えば,Sqrt[x]として平方根を計算すると,x が正の値ならば,実数 x に対する平方根は実数になる.しかし,x が負の値のときは複素数になってしまう.
Compileは常に同じ型の値を返さなければいけない.もし,Compileにより生成されたコードを実行して,実際に返ってくる型が前提とした型と違うときは,このコードによる計算は無効にされ,代りに標準式による計算が行われる.
この場合,最適化したコードは使われない.もとの標準式が使われる:
Compileの重要な機能のひとつに,数学的な式だけでなくプログラム的なものもコンパイルすることができる点が挙げられる.例えば,Compileは条件式や制御フローの構造体も扱うことができる.
コンパイルするものが何であれ,コンパイル関数Compile[vars,expr]に与えた引数は評価されることなくコンパイル処理に直接回される.したがって,式の代りにプログラムもコンパイルすることができる.
ニュートン近似法に基づいた平方根の計算プログラムをコンパイルする:
実行してみる: