今回は、Mathematica の即時定義と遅延定義について。これに関する今のバージョンのマニュアルの説明は、
http://reference.wolfram.com/mathematica/tutorial/ImmediateAndDelayedDefinitions.html
で読むことができる。これは非常に丁寧で、これ以上、僕が追加するようなことはまぁないのだけど、蛇足を承知で書いてみるの巻。

Set と SetDelayed

以前にも書いたように、Mathematica ではすべてを 頭部[引数1, 引数2, 引数3...] という形の「式」として扱っており、即時定義の = は Set[左辺, 右辺] 、遅延定義の := は SetDelayed[左辺, 右辺] の省略表記だ。
この2つの関数 Set と SetDelayed の属性(Attributes)を見てみると、次のようになっていることが分かる。

TableForm[Attributes /@ #, TableHeadings -> {#, None}] &[{Set, SetDelayed}]

ここで着目するのはそれぞれの属性の1つ目。Set(=)では HoldFirst、これは第1引数つまり左辺のみを「未評価のまま取り扱う」ことを示し、右辺にあたる第2引数は評価してから取り扱うことを意味している。一方の SetDelayed(:=)にある HoldAll は、左辺も右辺も「未評価のまま取り扱う」という属性である。

というわけで、属性から分かる =(Set)と :=(SetDelayed)の違いとは、左辺(第1引数)に割り当てるのが、右辺(第2引数)を評価したものなのか未評価のままのものなのかの違いということだ。「即時」とか「遅延」とかいう言葉が、タイマーみたいな「タイミングをはかる何か」を想像させるのだけど、単に、割り当て右辺が未評価かどうかによる結果を「即時」とか「遅延」とか名付けているものと考えるとすっきりする。「即時定義」とか「遅延定義」とか呼ばずに、「評価形定義」「未評価形定義」みたいに覚えておくといいかも知れない。

In[n]:= と Out[n]=

このことが腑に落ちていると、入力セルの左に付くラベルが :=(SetDelayed)を使った形の In[n]:= というもので、出力セルのラベルが =(Set)を使った形の Out[n]= になっているのも容易に納得できる。入力とは評価する前の式だから「未評価形」だし、出力は評価した結果なのだから「評価形」なのだ。In[n] の内容が未評価の形で保持されていることは、??In や Definition[In] を評価することで確認できる。

遅延定義と即時定義の組み合わせ

良くある例に、数列を遅延定義と即時定義の組み合わせで定義するというのがある。例えばフィボナッチ数列であれば、こんな具合だ(これはもちろん例で、ver.3 以降の Mathematica には Fibonacci という専用関数がある)。

fib[1] = fib[2] = 1;
fib[n_] := fib[n] = fib[n - 1] + fib[n - 2]

このような定義をすれば、いちど計算した結果をキャッシュすることになって、計算速度的に有利になることがあるという説明は、読んだことのある人も多いだろう。

この定義文の2行目がどのように働くのかは、上記の属性の話を念頭に置きながら、:= と = をそれぞれ SetDelayed と Set に書き直すとすっきり分かる。

SetDelayed[fib[n_], Set[fib[n], fib[n - 1] + fib[n - 2]]]

まだ評価したことのない数値で fib[n] が評価されたときには、SetDelayed がその未評価形の第2引数を実行する。第2引数の Set は、そのときの n で fib[n] の特定値の評価割り当てを行なう。と言うわけだ。

評価した形を使って遅延定義

Mathematica で自前のコードを書くようになって程なくすると、ほとんどの人がたいていの関数定義を :=(SetDelayed)で行なうようになる。これは、引数のシンボルがグローバル変数としても使われていたときに、関数の定義内容が予想外のものになってしまう事故を防ぐためだ。

この態度が身に付いてしまうと、例えば、時間がかかる SimplifyIntegrate の結果を右辺に使って関数を定義したいようなときに、どうしようかと迷うことになる。Simplify を :=(SetDelayed)の右辺に直接使ってしまうと、その関数を使うたびに時間をかけて Simplify が実行されてしまうので、遅くて実用に堪えないなんてことがあるのだ。

もちろん、変数が使われていないことを確認した上で =(Set)を使って定義するとか、Simplify の結果を :=(SetDelayed)の関数定義の右辺にコピー&ペーストするなどすればいいのだが、実はこういうときには Evaluate が使える。Evaluate は Hold×× 属性の例外を指定する関数なので、:=(SetDelayed)の右辺(第2引数)で使うと、評価した形を使った遅延定義というのが実現する。(ちなみに、Evaluate を右辺の一部に使うというのはうまくいかないので注意)

(* f1 と f2 の右辺で Simplify の中は同じ *)
f1[x_] := Simplify[2 Tan[x]/(1 + Tan[x]^2)]
f2[x_] := Evaluate[Simplify[2 Tan[x]/(1 + Tan[x]^2)]]

この例では特にそうだが、多くの場合は :=(SetDelayed)でなく =(Set)を使ってしまえば済む話かもしれないのだけど...。

 

以上、...なんだかまとまりのない話になってしまったな...。

※この記事の内容は執筆者の個人的見解で、ヒューリンクスによる公式情報ではありません。[免責事項]

トラックバック

この記事へのトラックバックURL
http://blog.hulinks.co.jp/cgi/mt/mt-tb.cgi/477
内容に対しての関連性がみられないものは削除する場合があります

コメント一覧

SetDelayed (:=), Evaluate と Simplify, Integrate の組み合わせはよく使いますね。今回の記事で紹介されているように、複雑な式を Simplify で簡略化したあと関数定義するとき。Set を使うと、関数定義の右辺に思わぬ変数が入り込んでいて、思い通りの結果が得られないことがあります。SetDelayed (:=) と Evaluate の組み合わせは、同じ定義を他のプログラムに再利用する時に役立ちます。
ところで同じような関数に Rule (->) と RuleDelayed (:>) がありますが、こちらの使い分けは未だよく分からず、ほとんど使っていません。何か有用な例があれば教えてください。

ちさかさん、いつもありがとうございます。
Rule と RuleDelayed についても、考え方はまったく同じで、右辺(第2引数)が、規則作成時点で評価されているか未評価のままかの違いです。あまりうまい例じゃないかもしれませんが、Cases を使ってリスト内の偶数を抜き出す処理のときに、ついでに「その偶数のうちで3の倍数でもある数値の個数」を数えたい、というときは、こんな風に RuleDelayed を使う方法が考えられます(Rule だとうまくいきません)。

ilist = RandomInteger[{1, 100}, {100}];
n = 0;
Cases[ilist, i_?EvenQ :> (If[Mod[i, 3] == 0, n++]; i)]
n

RuleDelayed のヘルプにも良例がたくさんありますね。
http://reference.wolfram.com/mathematica/ref/RuleDelayed.html

コメントの投稿

Emailアドレスは表示されません。は必須項目です。
ヒューリンクス取り扱い製品の内容や購入に関するお問い合わせはヒューリンクスサイト連絡先へお願いいたします。投稿前にその他の注意事項もご覧ください。

HULINKS サイトの新着情報