SAD では必要な関数を随時定義して使うことができます。最も単純な例として、今、
f[x_] := x^2;
の様に定義すれば、
f[4] :fはその引き数を2乗する。 Out[1]:= 16 f[1 + a] :引き数は式でもよい。 Out[2]:= ((1+a)^2) f[3x + x^2] Out[3]:= (((3 x)+(x^2))^2)
となります。ここで、上の定義式の左辺の x_ につけるアンダースコア(_)はきわめて重要な意味を持っています。シンボル1_ という記号は、SAD では、「シンボル1という名前を持ったパターン原子」とみなされます。もしアンダースコアを書き落とすと、
f[x] := x^2; :左辺の引き数に_がない。 f[4] Out[1]:= f[4] :fはもはや2乗の関数ではない。 f[x] Out[2]:= (x^2) :それでも引き数が x ならば右辺の値になる。
のように、全然違った結果になります。実は後者のような書き方もエラーではなく、有用な場合があります。
x_ というパターンは、関数の評価に際して、
a := x^3; f[x_] := x^2 + a; f[4] Out[1]:= (16+(x^3))
の様に、f[4]はまず、右辺の x を引き数 4 で 4^2 + a と置き換えてからその評価にかかります。シンボル a を評価すると同じ名前のシンボル x に出会いますが、これはもはや引き数の値とは関係がありません。もちろん、
f[x_] := (a := x^3; x^2 + a); f[4] Out[1]:= 80
とすれば、今度は右辺の a の定義の中に x があらわに書かれているのでそれは引き数に置換されています。ちなみに、上で a := x^3 のように a の定義が SetDelayed で書かれているので、a を定義した時点では a の右辺は評価されません。にもかかわらず、a の定義は、この場合、x^3 ではなく、4^3 になっています。つまり、引き数の置換はあくまで置き換えであって、評価ではないということです。置き換えは右辺のなかの全ての場所で同時に起こり、それを阻止することはできません。
SADでは関数の定義に際して、その引き数の形態を選ぶことにより、同一のシンボルに対して異なった定義を与えることができます。最も簡単な例として、
f[x_] := Sin[x] / x; f[0] = 1;
と関数 f を定義したとします。もし第1行だけだとすると、引き数が 0 の時に0/0となり、実行エラーを生じます。そこで第2行のように引き数の特殊な値に対する定義を書き加えるだけで、引き数が 0 の時には第2行の定義が実行され、エラーを防止できます。SAD はある関数を評価する場合、その引き数が関数の定義の引き数のパターンと照合し、照合すればその定義を実行します。この場合、より特殊な定義から順に照合します。従って、上の例では引き数 0 はどちらのパターンにも照合しますが、第2行の方が第1行よりも特殊なので第2行が優先的に照合され、実行されることになります。
もちろん上のような単純な場合は定義を一つにして、右辺の中に条件判断を設けても同じ結果を得ることができます。しかし、一般にはパターンの違いにより定義を区別するほうが記述が簡素になり、実行の効率も向上します。また、以下に見るように、引き数の個数の違いや式の形態などもパターンによれば簡単に区別できます。
あるシンボルに対する関数の定義を解除したい場合は Clear や Unset (=.) を用います。
以下に「パターン式」というのは、パターンを含んでいるかもしれない式を指します。パターンでない一般の式は自分自身と同じものだけに照合します。
x__ というパターンは、関数の評価に際して、
という動作をします。ここで「系列」Sequence とは特殊なリストで、他のリストあるいは式の中に入ると、その要素がそのまま親のリストの要素になる、という性質をもっています。例えば、
f[x__] := {x};
と関数 f を定義すると、f[1] は {1}、f[1, 2, 3] は {1, 2, 3} 等々を返します。
照合に際しては最初のパターンから、系列の長さ1の場合から順に試されます。例えば、
f[x__,y__] := {{x}, {y}};
f[1, 2, 3]
Out[1]:= {{1},{2,3}} の様に照合します。
x___ というパターンは、関数の評価に際して、
という動作をします。ただし、照合に際しては最初のパターンから、系列の長さ0の場合から順に試されます。例えば、
f[x___,y___] := {{x}, {y}};
f[1, 2, 3]
Out[1]:= {{},{1,2,3}}
の様に照合します。
_, __, ___ というパターンは照合の性質はそれぞれ上に述べたものと同じですが、右辺の評価の際には何らの置き換えも起しません。これらは引き数の形態のみを判定する場合には有用です。
例えば、x_Real は実数に対してだけ照合します。
x : パターン式1 は関数の評価に際して、
例: f[p : a|b|c] := Print[p];は引き数が a, b, c のいずれかである場合にだけそれを Print します。
Clear[a,f]; f[p : a] := Print[p]; (ア) a = 1; (イ) f[1] (ウ) Out[4]:= f[1]のように、(ア) の時点では a には値がないため、この関数の引き数はシンボル a に対してだけ照合します。従って、(イ) で a に後から値が設定されても、 (ウ) のように、その新しい値には照合しません。
テスト1[照合した値]を評価し、その結果が True (0でない実数)ならば、元の引き数が パターン式1 ? テスト1 に照合したと判定します。
一般にパターン式(パターンを含む式)のなかには同じシンボルを持つパターンを含めても構いません。その場合は、同一のシンボルは同一の対象に照合しなければならないという条件があります。例えば、f[1, 2] はf[x_, y_] には照合しますが、f[x_, x_]には照合しません。
さて、多くの関数ではその引き数は関数の定義との照合に先立って評価されます。しかし、例えば関数 Set の左辺、SetDelayed の両辺、Table の各引き数のように引き数を評価せず、そのままの形で関数に渡さなければならない場合も生じます。このような引き数の評価の属性は SetAttributes で指定することができます。 また、関数 Evaluate, Unevaluatedにより、個々の場合の評価の有無を指定することもできます。
ある関数を引用するときに f[... ,, ...] の様にカンマの間に何も書かない場合は、そこにシンボル Null があるとみなされます。
さて、これまで述べてきたシンボルへの関数の定義の割り当てはあくまでそのシンボルが関数の頭部になるようなものに限られていました。これに対して関数を割り当てられるシンボルが別の式の引き数に書かれているような関数の定義の仕方があります。例えば今、演算子 UpSetDelayed (^:=) により、
(seed = x_) ^:= SeedRandom[x];
とすると、以後、seed = n という式は SeedRandom[n] を実行することになります (SeedRandom[] 参照)。ここで、UpSetDelayed の左辺の頭部は Set ですから、シンボル seed はその第一引き数に現われています。このような引き数のシンボルに対する関数の割り当てを上方値 upvalue の割り当てと呼び、きわめて応用範囲の広いものです。(これに対して通常の関数の定義を下方値 downvalue を呼びます。
上方値の定義の解除は Clear[] または TagSet[] により行います。
SAD ではある式の一部を別の式に置き換えることができます。このような置換は、すでに関数の評価の際に引き数のパターンに対して実行されていることをみました。置換は ReplaceAll (演算子 /.) を用いて、どのような式に対しても実行できます。
式0 /. パターン式1 -> 式1 は
という作用をします。例: {a, b} /. x_ -> x + 1 ⇒ {a + 1, b + 1}。
演算子 /. の右辺には規則を与えます。規則とは、頭部が Rule (->1) または RuleDelayed (:>1) の式、あるいは そのような式達から成るリストです。
式0 //. 規則1 は置換が行われる限り、規則1による置換を続行します。
With はある式の一部を別の式で置き換えた後にその式を評価します。
例えば、SetDelayed や RuleDelayed の右辺のように普通は評価されない式でも、With を使えばその一部だけを評価することができます。また、With は複雑な式を見やすくするためにも有効です。更に、式の中で定数になる部分をあらかじめ評価しておくことにより、実行の効率を向上させることもできます。
With は後に述べる Module や Block に構造が似ていますが、機能はまったく違います。後者達が局所的なシンボルを生成するのに対して、With はそのような作用はしません。例えば、上の式1に現われるシンボルは照合の時に使われるだけで、式0 の評価の時には 式1 そのものは既に置換されています。
関数の定義では、多くの場合その関数の内部だけで使用するシンボルが必要になります。そのようなシンボルを局所シンボルと呼びます。局所シンボルは通常関数 Module で定義します。Module には二つの引き数があり、Module[{局所シンボル1, ...}, 式1] の様に用います。第一の引き数は局所シンボルのリストです。これにより、式1 の中に現われる同じ名前のシンボルは全てこの局所シンボルであるとみなされ評価されます。Module はそのように式1を評価しその結果を返します。例えば、
a := b; (ア)
Module[{b}, (イ)
b = c; (ウ)
a + b (エ)
]
としますと、(ア)は Module の外で単にシンボル a にシンボル b を割り当てています。ここで SetDelayed が用いられているため、右辺のシンボル b はまだ評価されていません。(イ)は この Module の中だけで有効な局所シンボル b を定義しています。(ウ)と(エ)がこの Module の第二引き数にあたります。まず、(ウ)では局所シンボル b に別のシンボル c を割り当てています。次に(エ)ではシンボル a と局所シンボル b の和を求めてそれをこの Module の結果にしようとしていますが、その結果は b + c になり、c + c ではありません。シンボル a にはシンボル b が評価されずに割り当てられており、シンボル b が評価されると一見シンボル c になりそうに思えますが、実は{\gt Module の外で書かれたシンボルと Module の内側で定義された局所シンボルは全く別のものである}という規則があるので、上のような結果になります。
局所シンボルは Module の実行が終わると、それへの値の割り当ては全て解除されます。
関数 Block は局所シンボルは定義せず、大域的シンボルの値だけを局所的に設定・変更するのに用います。宣言された大域シンボルの値は Block の終了とともに元に戻ります。
ユーザーは関数の定義をファイルに書くことによりライブラリを構築できます。そのようなファイルは単に SAD の入力ファイルと同じ書式で書けばよいのです。ただし、複数の式を並べる場合には、各々の式はセミコロン (CompoundExpression 参照) で区切らなければなりません。さもないと、式と式の間に演算子 Times があるとみなされます。
ライブラリのロードには Get か AutoLoadを用います。