Skip to main content

Actor クラス

Actor クラスは、Actor (Canister スマートコントラクト)のネットワークを プログラム的に 作成することを可能にします。 現在のところ、Actor クラスは別々のソースファイルに定義される必要があります。 Actor クラスの定義とインポートの仕方を説明するため、以下の例では Nat 型のキーからから Text 型の値への Map を実装します。 これは、キーと値の簡単な操作(挿入とルックアップ)のために put(k, v)get(k) の関数を持つ Actor クラスとなります。

この例では、データを分散させるために、キーのセットを n 個のバケットに分割します。ここでは、n = 8に固定します。 キー k のバケット i は、 kn で割った余り、つまり i = k % n で決定されます。 i番目のバケット([0..n]+i+ 番目)は、そのバケットのキーに割り当てられたテキスト値を格納するための専用の Actor を受け取ります。

バケット i に割り当てられる Actor は、サンプルの Buckets.mo ファイルで定義されている Actor クラスである Bucket(i) のインスタンスとして、以下のように取得されます:

Buckets.mo:

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 へとアップデートします。

どちらの関数も、クラス変数である ni を使って、キーがバケットに適しているかどうかを ((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 は:

  • キー kn で割った余りを使って、そのキーに対応するバケットのインデックス i を決定します。

  • i 番目のバケットが存在していなければ、null を返します。

  • i 番目のバケットが存在していれば、bucket.get(k, v) を呼び結果を返します。

put(k, v) 関数では、関数では、Map Actor は:

  • キー kn で割った余りを使って、そのキーに対応するバケットのインデックス 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 のクライアントは、コンストラクタに引数を渡すことで、ネットワーク内のバケットの(最大)数を自由に決定できるようになりました。

note

On the Internet Computer, calls to a class constructor must be provisioned with cycles to pay for the creation of a principal. See ExperimentalCycles for instructions on how to add cycles to a call using the imperative ExperimentalCycles.add(cycles) function.

Configuring and managing actor class instances

On the Internet Computer, the primary constructor of an imported actor class always creates a new principal and installs a fresh instance of the class as the code for that principal.

To provide further control over actor class installation, Motoko endows each imported actor class with an extra, secondary constructor. This constructor takes an additional first argument that specifies the desired installation mode. The constructor is only available via special syntax that stresses its system functionality.

Using this syntax, it's possible to specify initial canister settings (such as an array of controllers), manually install, upgrade and reinstall canisters, exposing all of the lower-level facilities of the Internet Computer.

See Actor class management for more details.