はじめに
数式処理システムMagmaは,関数の外の変数を読むことはできるが,値を書き込むことはできない.
a := 5; // この時点で変数aが定義されていないといけないし,その時点での値で固定される.ただしevalを使えば動的な値取得が可能.
// 読み込みはOK
procedure foo()
print a; // 関数foo内で変数aが定義されていないとき,外で定義されている値が用いられる
end procedure;
foo(); // 5
// 書き込みはNG
procedure bar()
a := 7; // グローバル変数ではなくローカル変数として扱われる
end procedure;
bar();
print a; // 5 (7にはならない)
...と思われていたが,どうやら属性(Attribute)を活用すれば,若干面倒ではあるがグローバル変数が実現できるようだ.
属性について
Magmaのストラクチャに対して,属性と呼ばれる値がある.
例えば,型FldFin
はストラクチャの型であるが,これに対してPowerPrinting
という属性が定義されている.
ListAttributes(FldFin);
/* Result:
FunctionFieldCategory PrimitiveElement UsePHLog
PowerPrinting StarAlgebraExt
PrimPair StrLocalData
*/
属性の値の取得・書き込みは次のように行う.
F<ep> := GF(16); // ストラクチャ型の変数を生成
print F`PowerPrinting; // 値取得
// F `` "PowerPrinting"; でも可
F`PowerPrinting := false; // 値書き換え
print F`PowerPrinting; // 値取得
PowerPrinting
属性は,有限体の原始多項式による代数拡大体について,その体上の元を拡大の生成元のべきとして表すか,多項式基底表現として表すかを選ぶ属性である.
k := GF(2);
d := 4;
kd<ep> := ext<k | PrimitivePolynomial(k, d)>;
a := Random(kd);
print kd`PowerPrinting, a; // true ep^10
kd`PowerPrinting := false;
print kd`PowerPrinting, a; // false ep^2 + ep + 1
新規属性の設定
属性を新たに追加するAddAttribute
という関数が用意されている.
ListAttributes(RngInt);
AddAttribute(RngInt, "abc");
"";
ListAttributes(RngInt);
/* Result:
FieldCategory LMGSchreierBound voronoi_stuff
FunctionFieldCategory LocSingTrans
GalBlock StrLocalData
FieldCategory LMGSchreierBound abc
FunctionFieldCategory LocSingTrans voronoi_stuff
GalBlock StrLocalData
*/
確かに,abc
という属性が追加されている.
この値に書き込みを行うには次のようにする.
AddAttribute(RngInt, "abc");
Z := Integers();
assigned Z`abc; // false, まだ値が設定されていない
Z`abc := 5;
assigned Z`abc; // true, 値が設定された
Z`abc; // 5
/*
Integers()`abc; だとエラーになる!
属性を書き換えるときは変数に格納してからでないといけない.
*/
この機能の面白いところは,属性の設定が全体で共有されたりされなかったりするということである.
AddAttribute(FldFin, "abc");
F<ep> := GF(16);
Z := GF(16);
F`PowerPrinting := false;
F`abc := 2;
K := GF(16);
Z`PowerPrinting; // false, つまりFでのPowerPrintingの設定が反映されている
Z`abc; // 2, つまりabc属性の設定も反映されている
K`PowerPrinting; // false
K`abc; // 2
AddAttribute(FldFin, "abc");
k := GF(2);
F<ep1> := ext<k | PrimitivePolynomial(k, 4)>;
Z<ep2> := ext<k | PrimitivePolynomial(k, 4)>;
F`PowerPrinting := false;
F`abc;
K<ep3> := ext<k | PrimitivePolynomial(k, 4)>;
Z`PowerPrinting; // true, Zを定義する後の設定は反映されない
Z`abc; // エラー, 設定は反映されず未定義だった
K`PowerPrinting; // false, Kを定義する前の設定なので反映された
K`abc; // エラー, 設定は反映されず未定義だった
上記のややこしい現象は置いておいて,重要なのはこれが関数でも同様に起こるということである.
AddAttribute(FldFin, "abc");
procedure foo()
k := GF(16);
k`abc := 7;
end procedure;
F := GF(16);
F`abc := 2;
F`abc; // 2
foo();
F`abc; // 7
つまり,属性として変数の値を格納しておけば,属性の値を書き換えることによって変数の値書き換えが実現する.
また,有限体ではなく整数Integers()
も利用可能である.
AddAttribute(RngInt, "abc");
procedure foo()
k := Integers();
k`abc := 7;
end procedure;
F := Integers();
F`abc := 2;
F`abc; // 2
foo();
F`abc; // 7
こちらの方が有限体と違って標数などのパラメータを与えずにすむので扱いやすい.
これまでの内容を踏まえ,次のように「グローバル変数」を実現した.
グローバル変数の実装
'::GLOBAL::Structure' := func< | Integers(), RngInt>;
// RngIntの既存の属性と名前衝突しないための識別子
function '::GLOBAL::get_fullname'(name)
return "::" cat name;
end function;
// 値書き込み
procedure '::GLOBAL::set'(name, value)
n := '::GLOBAL::get_fullname'(name);
obj, str := '::GLOBAL::Structure'();
AddAttribute(str, n); // 属性nが既存でも問題ない
obj `` n := value;
end procedure;
// 値取得
function '::GLOBAL::get'(name)
return '::GLOBAL::Structure'() `` ('::GLOBAL::get_fullname'(name));
end function;
global_set := '::GLOBAL::set';
global_get := '::GLOBAL::get';
最後の本当にグローバル変数としての機能を有するかを確認する.
procedure foo()
global_set("nnn", "OK");
end procedure;
procedure bar(n, v)
assert Type(v) eq MonStgElt;
_ := eval Sprintf("global_set(\"%o\", \"%o\"); return 0;", n, v);
end procedure;
global_set("nnn", 5);
print global_get("nnn"); // 5
foo();
print global_get("nnn"); // OK
bar("nnn", "OK 2");
print global_get("nnn"); // OK 2
きちんと関数内で外の変数の書き換えができている.