Actor classes:

Actor classes能够允许你以编程的方式创建canister智能合约。目前,actor classes不得不定义在一个单独的.mo源文件中。为了说明如何定义并导入(import) actor classes,接下来的例子演示一个以Nat类型为键映射到Text类型的分布式map。它提供简单的插入put(k, v)和查询函数get(k),用于处理这些键和值。

为了分配该示例的数据,一系列键值被划分到n个容器(buckets)中。现在,我们仅设置n = 8.键值k对应的容器i由n除以k的余数来决定,即i = k % n。第i个容器( i in [0..n) )接受一个被分配的actor来存储该键值所对应的text值。

正如被定义在样例Buckets.mo文件中的那样,负责第i个容器的actor被作为actor class 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);
  };

};

A bucket stores the current mapping of keys to values in a mutable map variable containing an imperative RedBlack tree, map, that is initially empty.

On get(k), the bucket actor simply returns any value stored at k, returning map.get(k).

On put(k, v), the bucket actor updates the current map to map k to ?v by calling map.put(k, v).

Both functions use the class parameters n and i to verify that the key is appropriate for the bucket by asserting ((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);
  };

};

As this example illustrates, the Map code imports the Bucket actor class as module Buckets.

The actor maintains an array of n allocated buckets, with all entries initially null. Entries are populated with Bucket actors on demand.

On get(k, v), the Map actor:

  • uses the remainder of key  k divided by n to determine the index i of the bucket responsible for that key
  • returns null if the ith bucket does not exist, or
  • delegates to that bucket by calling bucket.get(k, v) if it does.

On put(k, v), the Map actor:

  • uses the remainder of key  k divided by n to determine the index i of the bucket responsible for that key
  • installs bucket i if the bucket does not exist by using an asynchronous call to the constructor, Buckets.Bucket(i), and, after awaiting the result, records it in the array buckets
  • delegates the insertion to that bucket by calling bucket.put(k, v).

While this example sets the number of buckets to 8, you can easily generalize the example by making the Map actor an actor class, adding a parameter (n : Nat) and omitting the declaration let n = 8;. For example:

actor class Map(n : Nat) {

  type Key = Nat
  ...
}

现在,Map actor class的使用者可以通过构建actor时传递一个参数在网络中自由决定容器的(最大)数量。

Motoko NFT_2
- Motoko -

A Student on the way to full stack of Web3.