Internet Computer のプログラミング・モデルは, Candid の値をコード化したバイナリ・データの非同期メッセージ交換によって通信する, メモリ隔離されたキャニスタ・スマート・コントラクトから成っています. 一つのキャニスタ・スマート・コントラクトは, 一度に1つのメッセージを処理し, 競合状態を回避します. キャニスタ・スマート・コントラクトは, 自分が発したキャニスタ・スマート・コントラクト間メッセージの戻り値を元に次に何をすべきかをコールバックとして登録します.

The programming model of the Internet Computer consists of memory-isolated canister smart contracts communicating by asynchronous message passing of binary data encoding Candid values. A canister smart contract processes its messages one-at-a-time, preventing race conditions. A canister smart contract uses call-backs to register what needs to be done with the result of any inter-canister smart contract messages it issues.

Motoko は Internet Computer の持つ複雑さを アクタ・モデル という, よく知られた高度な抽象を用いて抽象化しています. 各キャニスタ・スマート・コントラクトは型付けされたアクタとして表現されます. アクタの型は, 自分が扱えるメッセージの一覧です. 各メッセージは, 型付けられた非同期関数として抽象化されています. アクタ型から Condid の型への翻訳によって, 下位層の Internet Computer の生のバイナリ・データ上の構造が決められます. アクタはオブジェクトに似ていますが, 異なるのは, 状態が完全に隔離されていること, 世界との相互作用が完全に非同期メッセージ交換によること, たとえ並行に動作するアクタが並列にメッセージを送ってきたとしても, メッセージは一度に1つずつ処理されることです.

Motoko abstracts the complexity of the Internet Computer with a well known, higher-level abstraction: the actor model. Each canister smart contract is represented as a typed actor. The type of an actor lists the messages it can handle. Each message is abstracted as a typed, asynchronous function. A translation from actor types to Candid types imposes structure on the raw binary data of the underlying Internet Computer. An actor is similar to an object, but is different in that its state is completely isolated, its interactions with the world are entirely through asynchronous messaging, and its messages are processed one-at-a-time, even when issued in parallel by concurrent actors.

Motoko では, アクタへのメッセージ送信は関数呼び出しですが,

  1. 呼び出しが戻るまで呼び出し元がブロックされるのではなく, メッセージは呼び出し先のキューに入れられ, 保留中のリクエストを表す フューチャ が呼び出し元に即時に返されます.フューチャは, 呼び出し元が後で (必要になったときに) 問い合わせできるような, リクエストの将来の結果の仮置場です.
  2. リクエストを送信してから, 結果が必要になるまでの間, 呼び出し元は自由に他の仕事 (別のリクエストを同じ, あるいは別のアクタに送るなど) ができます.
  3. いったん呼び出し先がリクエストを処理してしまえば, フューチャは完了し, その結果は呼び出し元が利用可能になります. 呼び出し元がフューチャを待っているとしたら, その実行はその結果と共に回復しますし, そうでなければ, その結果は後で使えるようにフューチャの中に格納されます.

In Motoko, sending a message to an actor is a function call, but instead of blocking the caller until the call has returned, the message is enqueued on the callee, and a future representing that pending request immediately returned to the caller. The future is a placeholder for the eventual result of the request, that the caller can later query. Between issuing the request, and deciding to wait for the result, the caller is free to do other work, including issuing more requests to the same or other actors. Once the callee has processed the request, the future is completed and its result made available to the caller. If the caller is waiting on the future, its execution can resume with the result, otherwise the result is simply stored in the future for later use.

Motoko では, アクタには専用の構文と型があります. それは,

(例えば) オブジェクトや可変配列を送るなどメッセージによって共有状態が紛れ込むのを避けるために, 共有関数を通じて送信されるデータは不変な 共有 型に制限されます.

In Motoko, actors have dedicated syntax and types; messaging is handled by so called shared functions returning futures (shared because they are available to remote actors); a future, f, is a value of the special type async T for some type T; waiting on f to be completed is expressed using await f to obtain a value of type T. To avoid introducing shared state through messaging, for example, by sending an object or mutable array, the data that can be transmitted through shared functions is restricted to immutable, shared types.

まず始めに, いちばん簡単な状態を持つサービス - 以前にローカルで動かした counter オブジェクトの分散ヴァージョンである Counter アクタから始めましょう.

To start, we consider the simplest stateful service: a Counter actor, the distributed version of our previous, local counter object.

Example: a Counter service

次のアクタ宣言をご覧下さい:

Consider the following actor declaration:

actor Counter {

  var count = 0;

  public shared func inc() : async () { count += 1 };

  public shared func read() : async Nat { count };

  public shared func bump() : async Nat {
    count += 1;
    count;
  };
};

Counter アクタはひとつのフィールドと3つのパブリックな 共有 関数を宣言しています:

The Counter actor declares one field and three public, shared functions:

共有関数はローカル関数とは異なり, 遠隔の呼び出し元にアクセスできますが, 引数と返値は 共有 型でなければならないという独自の制約もあります. 共有型には, 不変データ, アクタ参照, 共有関数参照などがありますが, ローカル関数への参照や可変データは含みません. アクタによる通信はすべて非同期なので, アクタの関数はフューチャ, つまりある型 T に対して async T 形式の型を返す必要があります.

Shared functions, unlike local functions, are accessible to remote callers and have additional restrictions: their arguments and return value must be shared types - a subset of types that includes immutable data, actor references, and shared function references, but excludes references to local functions and mutable data. Because all interaction with actors is asynchronous, an actor’s functions must return futures, that is, types of the form async T, for some type T.

Counter アクタの状態 (count) を読んだり, 変更したりするには, その共有関数を通じて行うしかありません.

The only way to read or modify the state (count) of the Counter actor is through its shared functions.

async T の値がフューチャです. フューチャの生成側は結果 (値かエラーか) を返すときにフューチャを完了します.

A value of type async T is a future. The producer of the future completes the future when it returns a result, either a value or error.

オブジェクトやモジュールとは異なり, アクタが公開できるのは関数だけで, しかもこの関数は 共有 できるものでなければなりません. この理由から, Motoko はパブリックなアクタ関数に shared 修飾子を付けなくてもより簡潔に同等なアクタ宣言ができるようにしています:

Unlike objects and modules, actors can only expose functions, and these functions must be shared. For this reason, Motoko allows you to omit the shared modifier on public actor functions, allowing the more concise, but equivalent, actor declaration:

actor Counter {

  var count = 0;

  public func inc() : async () { count += 1 };

  public func read() : async Nat { count };

  public func bump() : async Nat {
    count += 1;
    count;
  };
};

今のところ, 共有関数を宣言できるのは, アクタかアクタ・クラスの本体の中だけです. この制約にも拘わらず, 共有関数は Motoko の中では第一級の値で, 引数や返値として渡したり, データ構造の中に格納したりすることができます.

For now, the only place shared functions can be declared is in the body of an actor or actor class. Despite this restriction, shared functions are still first-class values in Motoko and can be passed as arguments or results, and stored in data structures.

共有関数の型は, 共有関数型を用いて指定されます. 例えば, 値 inc の型は shared () → async Nat になり, これを他のサービスへの単独のコールバックとして用いることができます (例は公開-購読 を見てね).

The type of a shared function is specified using a shared function type. For example, the value inc has type shared () → async Nat and could be supplied as a standalone callback to some other service (see publish-subscribe for an example).

Actor types

オブジェクトがオブジェクト型を持つのと同様に, アクタは アクタ型 を持ちます. Counter アクタは次のような型を持っています:

Just as objects have object types, actors have actor types. The Counter actor has the following type:

actor {
  inc  : shared () -> async ();
  read : shared () -> async Nat;
  bump : shared () -> async Nat;
}