A comprehensive overview
A simple, useful language for the Internet Computer (IC)
async
/await
for sequential programming of
asynchronous messagingInspirations: Java(Script), C#, Swift, Pony, ML, Haskell
wasm
libary)Identifiers:
x
, foo_bar
, test'
,
List
, Map
Parentheses for grouping
Type annotations (to help type inference):
(42 : Int)
type Delta = Nat;
func print() {
Debug.print(Int.toText(counter));
};
let d : Delta = 42;
var counter = 1;
counter := counter + tmp;
print();
if (b) …
if (b) … else …
switch x { case (pat1) e1; …; case _ en }
while (b) …
loop …
loop … while (b)
for (pat in e) …
Int
Inferred by default for negative literals.
Literals: 13
, 0xf4
, -20
,
+1
, 1_000_000
Nat
Non-negative, trap upon underflow.
Inferred by default for non-negative literals
Literals: 13
, 0xf4
,
1_000_000
Nat8
, Nat16
, Nat32
,
Nat64
, Int8
, Int16
,
Int32
, Int64
Trap on over- and underflow. Wrap-on-trap and bit-manipulating operations available.
Needs type annotations (somewhere)
Literals: 13
, 0xf4
, -20
,
1_000_000
Float
IEEE 754 double precision (64 bit) semantics, normalized NaN
Inferred for fractional literals
Literals: 0, -10, 2.71
, -0.3e+15
,
3.141_592_653_589_793_12
No surprises here
- x
a + b
a & b
…
Char
, Text
Unicode! No random access.
'x'
, '\u{6a}'
, '☃'
,"boo"
, "foo \u{62}ar ☃"
"Concat" # "enation"
Bool
Literals: true
, false
a or b
a and b
not b
if (b) e1 else e2
Simple functions:
Int.toText : Int -> Text
multiple arguments and return values
divRem : (Int, Int) -> (Int, Int)
can be generic/polymorphic
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 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) } );
func() { … }
short for
func() : () = { … }
(Bool, Float, Text)
immutable, heterogeneous, fixed size
let tuple = (true, 1.2, "foo");
tuple.1 > 0.0;
let (_,_,t) = tuple;
?Text
is either a value of that type, or 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 };
}
Different syntax, same type as records
{ #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;
}
}
// the type of base/Int.mo
module {
toText : Int -> Text;
abs : Int -> Nat;
…
}
types and values like objects
but restricted to static content (pure, no state, …)
import Debug "mo:base/Debug";
import Int "mo:base/Int";
base
package provides basic features.
More libraries popping up!
Like object types, but marked as actor
:
type Receiver = actor { recv : Text -> async Nat };
type Broadcast = actor {
register : Receiver -> ();
send : Text -> async Nat;
}
sharable arguments and no or async result type.
“canister” ≈ “actor”
actor
typesshared
function typeimport 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;
};
}
a typical canister main file
async T
asychronous future or promise
introduced by async { … }
(implicit in async function declaration)
await e
suspends computation pending e
’s result
import Broadcast "ic:ABCDEF23";
actor Self {
public func go() {
Broadcast.register(Self);
};
public func recv(msg : Text) : async Nat {
…
}
}
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";
};
};
}
Principal
: identity of a user or canister/actor
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
Type definitions
do not create types,
but name existing types
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 <: t2
: t1
can be used wherever
t2
is expected
type List<T> = ?{head : T; tail : List<T>};
…
let l : List<Nat> = ?{head = 0; tail = ?{head = 1 ; tail = null }};
try … catch …
&
throw …
)Classes as functions returning objects:
class Counter(init : Int) {
private var state : Int = init;
public func inc() { state += 1; };
public func get() : Int { state; };
}
Class instantiation as function call (no new
):
let c = Counter(666);
c.inc();
let 667 = c.get();
class Dict< K, V > (cmp : (K,K)-> Int ) {
add(k: K, v: V) { ... };
find(k: K) : ? V { ... };
};
let d = Dict<Int,Text> (func (i:Int, j:Int) : Int = i - j);
d.add(1,"Alice");
let ? name = d.find(1);
intToNat32
debugPrintInt
(tie into
execution environment)hashInt
, clzNat32
actor Server {
private var clients : List<Client> = null;
private shared broadcast(message : Text) {
var next = clients;
loop {
switch next {
case null { return; }
case (?l) { l.head.send(message); next := l.tail; };
};
};
};
public func subscribe(client : Client) : async Post {
let cs = {head = client; var tail = clients};
clients := ?cs;
return broadcast;
};
};
type Server = actor { subscribe : Client -> async Post; };
actor class Client() = this {
private var name : Text = "";
public func start(n : Text , s : Server) {
name := n;
let _ = async {
let post = await s.subscribe(this);
post("hello from " # name);
post("goodbye from " # name);
}
};
public func send(msg : Text) {
debugPrint(name # " received " # msg # "\n");
};
};
test
let bob = Client();
let alice = Client();
let charlie = Client();
bob.start("Bob", Server);
alice.start("Alice", Server);
charlie.start("Charlie", Server);
output
[nix-shell:~/motoko/guide]$ ../src/moc -r chat.mo
charlie received hello from bob
alice received hello from bob
bob received hello from bob
charlie received goodbye from bob
alice received goodbye from bob
bob received goodbye from bob
charlie received hello from alice
alice received hello from alice
bob received hello from alice
charlie received goodbye from alice
alice received goodbye from alice
bob received goodbye from alice
charlie received hello from charlie
alice received hello from charlie
bob received hello from charlie
charlie received goodbye from charlie
alice received goodbye from charlie
bob received goodbye from charlie