Motoko 一览

本摘要提供了对 Motoko 的简单而全面的概述,并提供了一些关键特性示例,可帮助您识别您在其他语言中可能知道的操作和模式,以及它们在 Motoko 中的样子。

Motoko 动机和目标

DFINITY 和 Internet Computer platform 的一种简单、有用的语言。

  • 熟悉的语法

  • 默认安全

  • 使用 canister 模型结合智能合约

  • 提供 DFINITY 和 Internet Computer platform 功能的无缝集成

  • 充分利用现在和未来的 WebAssembly

关键设计点

Motoko 从多种编程语言中汲取灵感,包括 Java、JavaScript、C#、Swift、Pony、ML、Haskell。

  • 面向对象、函数式和命令式

  • 对象作为成员的记录

  • async/await 用于异步消息的顺序编程

  • 具有简单泛型和子类型的结构类型 *安全算术(无界和检查)

  • 默认情况下不可为空的类型

  • 类似 JavaScript 的语法,但静态类型和理智

语义

  • 按值调用(如 Java、C、JS 和 ML;不像 Haskell 和 Nix)

  • 声明是局部相互递归的

  • 参数化,有界多态性

  • 子类型作为包含,而不是强制 *没有动态演员表

  • 没有继承

实现

  • 在 OCaml 中实现(利用 wasm 库)

  • 简单的参考解释器

  • WebAssembly 不太简单的编译器

  • 多通道,每通道输入 IR

  • 统一表示,未装箱算术

  • 两个空格 gc,消息之间的 gc

  • 擦除多态性

语言特点

接下来的部分以简化的形式重点介绍 Motoko 编程语言功能。 有关使用这些和其他功能的更多信息,请参阅Language quick reference in the ink:../language-guide/motoko.html[Motoko编程语言指南]。

表达式

*标识符,例如`x`,foo_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 and if - else

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

switch and case

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

while and loop

while (b) …

loop …
loop … while (b)`

for

for (pat in e) …

原始类型

接下来的部分重点介绍 Motoko 编程语言中的原始类型。

无界整数

Int

  • 默认情况下推断为负文字

  • 文字: 13, 0xf4, -20, +1, 1_000_000

无限自然数

Nat

  • 非负数,下溢陷阱

  • 默认为非负文字推断

  • 文字:130xf41_000_000

有界数(陷阱)

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

  • 上溢和下溢陷阱

  • 需要指定类型注释

  • 文字: 13, 0xf4, -20, 1_000_000

浮点数字

Float

  • IEEE 754 双精度(64 位)语义,归一化 NaN

  • 推断小数字面量

  • 文字: 0, -10, 2.71, -0.3e+15, 3.141_592_653_589_793_12

数值运算

操作符合你的预期(没有意外)。

a - b
a + b
a & b

===字符和文本

`字符', `文本'

Unicode,无随机访问

'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

first-class (can be passed around, stored)

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

函数声明和使用

func() { … } short for func() : () = { … }

参数函数

类型实例化有时会被省略

匿名函数 (a.k.a. lambdas)

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] will trap (fixed size)
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] will trap (fixed size)

记录

{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; // private by default
  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 编程语言处理包和模块的示例。

模块

  • 类型和值,如对象

  • 仅限于 static 内容(纯,无状态,…​)

// the type of base/Int.mo
module {
  toText : Int -> Text;
  abs : Int -> Nat;
  …
}

模块导入

  • base 包提供基本功能

  • 在社区支持下不断发展的其他库

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

平台功能

接下来的部分提供了 Motoko 编程语言平台特定功能的示例。

Actor 类型

  • 类似对象类型,但标记为 actor

  • shareable 参数和 noasync 结果类型。

  • “canister” ≈ “actor”

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

可共享 ≈ 可序列化

  • 所有原始类型

  • 记录、元组、数组、变体、具有不可变可共享组件的选项

  • actor 类型

  • shared 函数类型

以下是不可共享的:

  • 可变的东西

  • 局部函数

  • 对象(带有方法)

完整的actor示例

典型的容器主文件

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;
  };
}

异步/等待

异步T

  • 异步未来或承诺

  • async { …​ } 引入(隐含在异步函数声明中)

  • await e 暂停计算等待 e 的结果

Actor导入

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

主体和调用

Principal 类型表示用户或容器/actor的身份

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); // works

子类型

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); // also works

t1 <: t2t1 可以在任何需要 t2 的地方使用

通用类型

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

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

错误处理

try … catch …

throw …

Language comparison cheat sheet

Literals

Motoko Ocaml JavaScript/TypeScript
> 3;
3 : Nat
# 3;;
- : int = 3
> 3
3
> 3.141;
3.141 : Float
# 3.141;;
- : float = 3.141
> 3.141
3.141
> “Hello world”;
“Hello world” : Text
# “Hello world”;;
- : string = “Hello world”
> “Hello world”
“Hello world”
> ‘J’;
‘J’ : Char
# ‘J’;;
- : char = ‘J’

Does not have char literals — use string

> true;
true : Bool
# true;;
- : bool = true
> true
true
> ();
# ();;
- : unit = ()
> undefined
> (3, true, “hi”);
(3, true, “hi”) : (Nat, Bool, Text)
# (3, true, “hi”);;
- : int * bool * string = 3, true, “hi”
> [3, true, “hi”]
[3, true, “hi”]
> [var 1, 2, 3];
[1, 2, 3] : [var Nat]
# [|1; 2; 3|];;
- : int array = [|1; 2; 3|]
> [1, 2, 3]
[1, 2, 3]
> [1, 2, 3];
[1, 2, 3] : [Nat]
# [1; 2; 3];;
- : int list = [1; 2; 3]
> [1, 2, 3]
[1, 2, 3]

Expressions

Motoko Ocaml JavaScript/TypeScript
-3*(1+7)/2%3
-3*(1+7)/2 mod 3
-3*(1+7)/2%3
-1.0 / 2.0 + 1.9 * x
-1.0 /. 2.0 +. 1.9 *. x
-1 / 2 + 1.9 * x
a || b && c
a or b and c
a || b && c

Functions

Motoko Ocaml JavaScript/TypeScript
func<T1,T2,T3>(f : (T1, T2) -> T3) : T1 -> T2 -> T3 = func(x : T1) : T2 -> T3 = func(y : T2) : T3 = f(x,y)
fun f -> fun x -> fun y -> f (x, y)
or
fun f x y -> f (x, y)
f => x => y => f(x,y)
func<T1, T2, T3>(f : (T1, T2) -> T3, x: T1, y : T2) : T3 = f (x,y)
fun (f, x, y) -> f (x, y)
([f, x, y]) => f(x,y)
func f<T>(x:T) : T = x
let f x = x
f(x) { x }

Does not have function pattern matching

func(x : Int) : Int =
  switch(x) {
    case (0) 0;
    case (n) 1;
  };
function 0 -> 0
         | n -> 1

Control flow

Motoko Ocaml JavaScript/TypeScript
if (3 > 2) “X” else “Y”
if 3 > 2 then “X” else “Y”
if (3 > 2) { “X” } else { “Y” }
import Debug “mo:base/Debug”;
if (3 > 2) Debug.print(“hello”);
if 3 > 2 then print_string “hello”
if (3 > 2) console.log(“hello”)
while (true) {
  Debug.print(“X”);
}
while true do
  print_string “X”
done
while(true) {
  console.log(“X”);
}
label L loop {
  if (x == 0) break L
  else continue L;
} while (true);

没有 do while 循环——使用递归或 while

do {
  if (x === 0) break;
  else continue;
} while (true);
import Iter “mo:base/Iter”;
for (i in Iter.range(1,10)) {
  Debug.print(“X”);
};
for i = 1 to 10 do
  print_string “X”
done
for (i = 1; i <= 10; i++) {
  console.log(“X”);
}
print_string “hello”;
print_string “world”
print_string “hello”;
print_string “world”
console.log(“hello”);
console.log(“world”);

Value declarations

Motoko Ocaml JavaScript/TypeScript
let name = expr;
let name = expr
const name = expr
let f = func<T1, T2>(x : T1) : T2 { expr };
let f x = expr
const f = x => expr
let fib = func(n : Nat) : Nat {expr};
let rec fib n = expr
const fib = n => expr

Type declarations

Motoko Ocaml JavaScript/TypeScript
type T = Int32 -> Bool
type t = int -> bool
<int is 31-bit signed int>
type t = (x: number) => boolean;
type AssocList<K,V> = List<(K,V)>
type (‘a, ‘b) assoc_list = (‘a * ‘b) list

not applicable

type option<T> = ?T

type ‘a option = None

Some of ‘a

type option<T> = T?

type T = {#a : Int32; #b : U}; type U = (T, T);

type t = A of int

B of u and u = t * t

not applicable

type Complex = {#c : (Float, Float)}; func complex(x : Float, y : Float) : Complex = #c(x,y); func coord(#c(x, y) : Complex) : (Float, Float) = (x, y);

Pattern matching

Motoko Ocaml
func get_opt<T>(opt : ?T, d : T) : T {
  switch(opt) {
    case (null) d;
    case (?x) x;
  };
}
let get_opt (opt, d) =
  match opt with
    None -> d
  | Some x -> x

Does not have guards — use if

import prelude “mo:base/Prelude”;
func fac(x : Nat) : Nat {
  switch(x) {
    case (0) 1;
    case (n) if (n>0) n * fac(n-1) else Prelude.unreachable();
  };
}
let rec fac = function
  0 -> 1
| n when n>0 -> n * fac(n-1)
| _ -> raise Hell

Does not have as a pattern

let foo ((x,y) as p) = (x,p,y)

Tuples

Motoko Ocaml JavaScript/TypeScript
type Foo = (Int32, Float, Text)
type foo = int * float * string
type foo = (number, number, string)
let bar = (0, 3.14, “hi”)
let bar = (0, 3.14, “hi”)
const bar = [0, 3.14, “hi”]
let x = bar.1
or
let (_, x, _) = bar
let _, x, _ = bar in x
const x = bar[1]

Records

Motoko Ocaml JavaScript/TypeScript
type foo = {x : Int32; y : Float; var s : Text}
type foo = {x:int; y:float; mutable s:string}

Everything is mutable

type foo = {
  x: number; y: number;
  s: string
}
let bar = {x=0; y=3.14; var s=””}
let bar = {x=0; y=3.14; s=””}
const bar = {x:0; y:3.14; s:””}
bar.x
bar.y
bar.s
bar.x
bar.y
bar.s
bar.x
bar.y
bar.s

Does not do pattern matching on mutable fields

let {x=x; y=y} = bar
let {y=y} = bar
or
let {x;y} = bar
let {y} = bar
let {x=x; y=y; s=s} = bar
let {y=y} = bar
or
let {x;y;s} = bar
let {y;_} = bar
bar.s := “something”
bar.s <- “something”
bar.s = “something”
type Bar = { f: <T>T -> Int32 }
type bar = { f:’a.’a->int }
type bar = {
  f<T>(x:T): number;
}

References and mutable variables

Motoko Ocaml JavaScript/TypeScript
var r = 0;
let r = ref 0
let r = new Number(0) // object reference
or
let r = 0  // mutable variable
r
!r
or
r.contents
r
r := 1
r := 1
or
r.contents <- 1
r = 1

Does not take mutable variables

func f(x : Nat) : Nat = x
let f {contents=x} = x
r1 == r2
r1 != r2
r1 == r2
r1 != r2
r1.valueOf() === r2.valueOf()
r1 !== r2

Comparison

Motoko Ocaml JavaScript/TypeScript
2 == 2
2 != 3
2 = 2
2 <> 3
2 === 2
2 !== 3

Does not have references

var r = 2;
var s = 2;
r == s
let r = ref 2
r == r
r != ref 2

Does not have generic equality

Does not have a generic equality

(2, r) != (2, r)
(2, r) = (2, ref 2)
(2, r) === (2, r)

Immutable and mutable arrays

Motoko Ocaml
import Array “mo:base/Array”;
Array.tabulate(20, func(x:Nat):Nat = x*x)
List.init 20 (fun x -> x*x)
Array.init(20, 1.0)
Array.make 20 1.0
a[2]
Array.get a 2

a.(2)
a[2] := x
Array.set a 2 x
or
a.(2) <- x
for (x in a.vals()) {
  Debug.print(x)
}
List.iter print_string a

Strings

Motoko Ocaml
“Hello ” # “world\n”
“Hello “ ^ “world\n”
Int.toText(13)
debug_show(3.141)
string_of_int 13
string_of_float 3.141
s.len()
String.length s
for (c in s.chars()) {
  Debug.print(debug_show(c))
}
String.iter print_char s

Does not have index access

String.get s 0 or s.[0]

Class declaration example

以下表格用以对比在类宣告在proglang,JavaScript/TypeScript中的区别 The following table compares class declarations in Motoko with class declarations in JavaScript and 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
}