Actor クラス
Actor クラスは、Actor (Canister スマートコントラクト)のネットワークを プログラム的に 作成することを可能にします。
現在のところ、Actor クラスは別々のソースファイルに定義される必要があります。
Actor クラスの定義とインポートの仕方を説明するため、以下の例では Nat
型のキーからから Text
型の値への Map を実装します。 これは、キーと値の簡単な操作(挿入とルックアップ)のために put(k, v)
と get(k)
の関数を持つ Actor クラスとなります。
この例では、データを分散させるために、キーのセットを n
個のバケットに分割します。ここでは、n = 8
に固定します。
キー k
のバケット i
は、 k
を n
で割った余り、つまり i = k % n
で決定されます。
i
番目のバケット([0..n]
の +i+
番目)は、そのバケットのキーに割り当てられたテキスト値を格納するための専用の Actor を受け取ります。
バケット i
に割り当てられる Actor は、サンプルの Buckets.mo
ファイルで定義されている Actor クラスである Bucket(i)
のインスタンスとして、以下のように取得されます:
import Nat "mo:base/Nat";
import Map "mo:base/RBTree";
actor class Bucket(n : Nat, i : Nat) {
type Key = Nat;
type Value = Text;
let map = Map.RBTree<Key, Value>(Nat.compare);
public func get(k : Key) : async ?Value {
assert((k % n) == i);
map.get(k);
};
public func put(k : Key, v : Value) : async () {
assert((k % n) == i);
map.put(k,v);
};
};
バケットは、キーと値の現在のマッピングを、可変変数である map
に格納します。map
は命令的な RedBlack ツリーであり、初期状態では空です。
get(k)
関数では、バケット Actor は k
に格納されている任意の値を map.get(k)
によって単純に返します。
put(k, v)
関数では、バケット Actor は map.put(k, v)
を呼ぶことで、現在の map
を、k
から ?v
にマップする map
へとアップデートします。
どちらの関数も、クラス変数である n
と i
を使って、キーがバケットに適しているかどうかを ((k % n) == i)
とアサートして検証します。
Map のクライアントは、以下のような調整役の Map
Actor と通信することができます:
import Array "mo:base/Array";
import Buckets "Buckets";
actor Map {
let n = 8; // number of buckets
type Key = Nat;
type Value = Text;
type Bucket = Buckets.Bucket;
let buckets : [var ?Bucket] = Array.init(n, null);
public func get(k : Key) : async ?Value {
switch (buckets[k % n]) {
case null null;
case (?bucket) await bucket.get(k);
};
};
public func put(k : Key, v : Value) : async () {
let i = k % n;
let bucket = switch (buckets[i]) {
case null {
let b = await Buckets.Bucket(n, i); // dynamically install a new Bucket
buckets[i] := ?b;
b;
};
case (?bucket) bucket;
};
await bucket.put(k, v);
};
};
この例が示すように、Map
コードは、Bucket
Actor クラスを Buckets
モジュールとしてインポートしています。
Actor は n
個の割り当てられたバケットの配列を保持しています。
エントリは最初は null
であり、必要に応じて Bucket
の Actor が入ります。
get(k, v)
関数では、関数では、Map
Actor は:
-
キー
k
をn
で割った余りを使って、そのキーに対応するバケットのインデックスi
を決定します。 -
i
番目のバケットが存在していなければ、null
を返します。 -
i
番目のバケットが存在していれば、bucket.get(k, v)
を呼び結果を返します。
put(k, v)
関数では、関数では、Map
Actor は:
-
キー
k
をn
で割った余りを使って、そのキーに対応するバケットのインデックスi
を決定します。 -
バケット
i
が存在しない場合は、コンストラクタであるBuckets.Bucket(i)
を非同期に呼び出してバケットを作成し、その結果を待ってから配列buckets
に記録します。 -
bucket.put(k, v)
を呼び出し,バケットへ挿入します。
この例ではバケットの数を 8
としていますが、Map
Actor を Actor class にして、パラメータ (n : Nat)
を追加し、let n = 8;
という宣言を省略することで簡単に一般化できます。例えば、以下のようになります:
actor class Map(n : Nat) {
type Key = Nat
...
}
Actor class Map
のクライアントは、コンストラクタに引数を渡すことで、ネットワーク内のバケットの(最大)数を自由に決定できるようになりました。