Motoko の概要

ここでは、読者の理解の助けになるように、他の言語で見られるオペレーションやパターンが Motoko ではどうなるのかをまとめています。主要な機能の例を挙げて Motoko の概要をシンプルに、かつ包括的に説明します。

Motoko の動機と目標

Motoko は、DFINITY と Internet Computer プラットフォーム のためのシンプルで使いやすい言語です。

  • よく見られるシンタックス

  • デフォルトで安全

  • Canister モデルを使用したスマートコントラクトの組み込み

  • DFINITY と Internet Computer プラットフォーム の機能のシームレスな統合を提供

  • 現在、そして将来の WebAssembly を最大限に活用

設計のポイント

Motoko は、Java、JavaScript、C#、Swift、Pony、ML、Haskell など、いくつかのプログラミング言語からインスピレーションを受けています。

  • オブジェクト指向、関数型、命令型

  • メンバーの record としてのオブジェクト

  • async/await による非同期メッセージングのシーケンシャルプログラミング

  • シンプルなジェネリクスと派生型付けによる構造的な型付け

  • 安全な算術 (Unbounded とチェック)

  • デフォルトでは Non-nullable 型

  • JavaScript のようなシンタックスでありながら、静的に型付けされており安全

セマンティクス

  • 値渡し(Java、C、JS、MLと同様で、HaskellやNixとは異なる)

  • 宣言は局所・相互再帰

  • パラメトリック・有界量化

  • 型強制ではなく包摂的な派生型付け

  • 動的キャストなし

  • 継承なし

実装

  • OCaml で実装(wasm ライブラリを利用)

  • シンプルなリファレンスインタプリタ

  • WebAssembly へのシンプルなコンパイラ

  • 各パスで型付けされた IR によるマルチパス

  • 統一された表現、Unboxed な算術

  • Two-space GC、メッセージ間 GC

  • 消去によるポリモーフィズム

言語の特徴

この章では、Motoko プログラミング言語の機能を簡単に紹介します。 ここで紹介している機能やその他の機能の使い方については、言語のクイックリファレンスMotoko プログラミング言語のガイド を参照してください。

  • xfoo_bartest'ListMap などの識別子

  • グルーピングのための括弧書き

  • 型推論を助けるための型アノテーション(例えば (42 : Int)

ブロックと宣言

  • 各宣言の後にセミコロンが必要

  • 相互再帰

  • 可変変数を明示的に記述

type Delta = Nat;
func print() {
  Debug.print(Int.toText(counter));
};
let d : Delta = 42;
var counter = 1;
counter := counter + tmp;
print();

制御フロー

if と if - else

if (b) …
if (b) … else …

switch と case

switch x { case (pat1) e1; …; case _ en }

while と loop

while (b) …

loop …
loop … while (b)`

for

for (pat in e) …

プリミティブ型

次の章では、Motoko プログラミング言語のプリミティブ型について説明します。

制限なし整数

Int

  • 負のリテラルを標準で推論

  • リテラル: 130xf4-20+11_000_000

制限なし自然数

Nat

  • 非負数、アンダーフロー時のトラップ

  • 非負のリテラルを標準で推論

  • リテラル: 13, 0xf4, 1_000_000

制限あり数(トラップあり)

Nat8, Nat16, Nat32, Nat64, Int8, Int16, Int32, Int64

  • オーバーフロー、アンダーフロー時にトラップ

  • 型アノテーションの指定が必要

  • リテラル: 13, 0xf4, -20, 1_000_000

浮動小数点数

Float

  • IEEE 754 の倍精度(64ビット)セマンティクス、正規化された NaN

  • 小数点以下のリテラルの推論

  • リテラル: 0-102.71-0.3e+153.141_592_653_589_793_12

数値演算

数値演算は期待通りの動作をします

a - b
a + b
a & b

文字とテキスト

CharText

ユニコード、ランダムアクセスなし

'x', '\u{\6a}', '☃'
"boo", "foo \u{\62}ar ☃"
"Concat" # "enation"

ブーリアン

Bool

リテラル: true, false

a or b
a and b
not b
if (b) e1 else e2

関数

次の章では、プログラミング言語 Motoko で関数を扱うための例を紹介します。

関数型

シンプルな関数

Int.toText : Int -> Text

複数の引数と返り値

divRem : (Int, Int) -> (Int, Int)

は、ジェネリクス、ポリモーフィックでは以下のようになります:

Option.unwrapOr : <T>(?T, default : T) -> T

第一級関数(引き渡し、保管が可能)

map : <A, B>(f : A -> B, xs : [A]) -> [B]
let funcs : [<T>(T) -> T] = …

関数の宣言と使用

func() { … }func() : () = { … } の短縮形

パラメトリック関数

型のインスタンス化は省略されることがあります。

匿名関数(ラムダ関数)

func add(x : Int, y : Int) : Int = x + y;
func applyNTimes<T>(n : Nat, x : T, f : T -> ()) {
  if (n == 0) return;
  f(x);
  applyNTimes(n-1, x, f);
}
applyNTimes<Text>(10, "Hello!", func(x) = { Debug.print(x) } );

複合型

次の章では、プログラミング言語 Motoko で複合型を扱う例を紹介します。

タプル型

(Bool, Float, Text)

イミュータブル、異なる型を格納可能、固定サイズ

let tuple = (true, 1.2, "foo");
tuple.1 > 0.0;
let (_,_,t) = tuple;

オプション型

?Text

は、その型の値か、null

func foo(x : ?Text) : Text {
  switch x {
    case (null) { "No value" };
    case (?y) { "Value: " # y };
  };
};
foo(null);
foo(?"Test");

配列型(イミュータブル)

[Text]

let days = ["Monday", "Tuesday", … ];
assert(days.len() == 7);
assert(days[1] == "Tuesday");
// days[7] はトラップ (固定長)
for (d in days.vals()) { Debug.print(d) };

配列型 (ミュータブル)

[var Nat]

let counters = [var 1, 2, 3];
assert(counters.len() == 3);
counters[1] := counters[1] + 1;
// counters[3] はトラップ (固定長)

レコード型

{name : Text; points : var Int}

let player = { name = "Joachim";  var points = 0 };
Debug.print(
  player.name # " has " #
  Int.toText(player.points) # " points."
);
player.points += 1;

オブジェクト

{ get : () → Int; add : Int → () }

レコード型と同じ型の異なるシンタックス

object self {
  var points = 0; // デフォルトでプライベート
  public func get() = points;
  public func add(p : Int) { points += p };
}

バリアント型

{ #invincible; #alive : Int; #dead }

列挙型と類似

type Health = { #invincible; #alive : Nat; #dead };
func takeDamage(h : Health, p : Nat) : Health {
  switch (h) {
    case (#invincible) #invincible;
    case (#alive hp) {
      if (hp > p) (#alive (hp-p)) else #dead
    };
    case (#dead) #dead;
  }
}

パッケージとモジュール

次の章では、プログラミング言語 Motoko でパッケージやモジュールを扱う例を紹介します。

モジュール

  • オブジェクトのような型と値

  • 静的 なコンテンツに限定(純粋、ステートなし、…​)。

// base/Int.mo の型
module {
  toText : Int -> Text;
  abs : Int -> Nat;
  …
}

モジュールのインポート

  • 基本的な機能を提供する base パッケージ

  • コミュニティのサポートにより開発されている追加のライブラリ

import Debug "mo:base/Debug";
import Int "mo:base/Int";

プラットフォームの機能

次の章では、プログラミング言語 Motoko のプラットフォーム固有の機能の例を紹介します。

Actor 型

  • オブジェクト型に似ていますが、actor としてマークされています。

  • sharable の引数と、no または async の返り値型。

  • “canister” ≈ “actor”

type Receiver = actor { recv : Text -> async Nat };
type Broadcast = actor {
  register : Receiver -> ();
  send : Text -> async Nat;
}

Sharable 型 ≈ シリアライズ

  • 全てプリミティブ型

  • レコード、タプル、配列、バリアント、オプションなどであり、イミュータブルで Sharable なコンポーネントであるもの

  • actor

  • shared 関数型

以下は sharable ではない:

  • ミュータブルなもの

  • ローカル関数

  • オブジェクト (メソッドを持つもの)

完全な Actor の例

典型的な Canister の main ファイル

import Array "mo:base/Array";
actor {
  var r : [Receiver] = [];
  public func register(a : Receiver) {
    r := Array.append(r, [a]);
  };
  public func send(t : Text) : async Nat {
    var sum := 0;
    for (a in r.values()) {
      sum += await a.recv(t);
    };
    return sum;
  };
}

Async/await

async T

  • 非同期の future や promise

  • async { …​ } によって導入(async 関数宣言では暗黙的)。

  • await ee の結果を待つために計算をサスペンドする。

Actor のインポート

import Broadcast "ic:ABCDEF23";
actor Self {
  public func go() {
    Broadcast.register(Self);
  };
  public func recv(msg : Text) : async Nat {
    …
  }
}

Principal と Caller

プリンシパル型は、ユーザーや Canister/Actor の ID を表します。

actor Self {
  let myself : Principal = Principal.fromActor(Self);
  public shared(context) func hello() : async Text {
    if (context.caller == myself) {
      "Talking to yourself is the first sign of madness";
    } else {
      "Hello, nice to see you";
    };
  };
}

型システム

次の章では、プログラミング言語 Motoko で使用されている型システムの詳細を紹介します。

構造

型定義は、型を作るのではなく、既存の型に名前を付けます。

type Health1 = { #invincible; #alive : Nat; #dead };
type Health2 = { #invincible; #alive : Nat; #dead };

let takeDamage : (Health1, Nat) -> Health1 = …;
let h : Health2 = #invincible;
let h' = takeDamage(h, 100); // 動作します

派生型付け

Mortal <: Health

type Health = { #invincible; #alive : Nat; #dead };
type Mortal = { #alive : Nat; #dead };

let takeDamage : (Health, Nat) -> Health = …;
let h : Mortal = #alive 1000;
let h' = takeDamage(h, 100); // こちらも動作します

t1 <: t2: t1t2 が期待されるところであればどこでも使用できます。

ジェネリクス型

type List<T> = ?{head : T; tail : List<T>};

let l : List<Nat> = ?{head = 0; tail = ?{head = 1 ; tail = null }};

エラーハンドリング

try … catch …

throw …

クラス宣言の例

次の表は、Motoko のクラス宣言と、JavaScript や TypeScript のクラス宣言を比較したものです。

Motoko JavaScript/TypeScript
class Counter(initValue:Nat) {
  var _value = initValue;
  public func get() : Nat {
    _value
  };
  func f(x: Nat) {};
}
class Counter {
  private _value;
  constructor(initValue) { _value = initValue }
  public get() { return _value }
  private f(x) {}
}
class Foo() = Self {
  func f() : Foo = Self
}