2021.5.3

Motoko笔记

1.【声明一个Object类】

例1:

object counter { 
    var count = 0; 
    public func inc() { count += 1 }; 
    public func read() : Nat { count }; 
    public func bump() : Nat { 
        inc(); 
        read() 
    }; 
};

In the declaration of object, the variable count was explicitly declared neither as public nor as private.

默认情况下,对象块中的所有声明都是私有的,比如这里的count。因此,count的类型不会出现在对象的类型中(如2.中所示),并且从外部无法访问它的名称和存在。

2.【Object Types】

例1中的Object类counter拥有以下type:

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

(包含花括号的这个整体就是其object type,⚠️能够表示在type中的一定是可访问的,私有成员的type不会出现在object type中)

Each field type consists of an identifier, a colon :, and a type for the field content. Here, each field is a function, and thus has an arrow type form (_ → _).

*3.【object之间“可交换”(类型兼容原则)】

The inaccessibility of this field comes with a powerful benefit: By not exposing this implementation detail, the object has a more general type (fewer fields), and as a result, is interchangeable with objects that implement the same counter object type differently, without using such a field.

To illustrate the point just above, consider this variation of the counter declaration above, of byteCounter:

import Nat8 "mo:base/Nat8"; 
object byteCounter { 
    var count : Nat8 = 0; 
    public func inc() { count += 1 }; 
    public func read() : Nat { Nat8.toNat(count) }; 
    public func bump() : Nat { inc(); read() }; 
};

This object has the same type as the previous one, and thus from the standpoint of type checking, this object is interchangeable with the prior one:

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

Unlike the first version, however, this version does not use the same implementation of the counter field. Rather than use an ordinary natural Nat that never overflows, but may also grow without bound, this version uses a byte-sized natural number (type Nat8) whose size is always eight bits.

As such, the inc operation may fail with an overflow for this object, but never the prior one, which may instead (eventually) fill the program’s memory, a different kind of application failure.

一般来说,在两个实现(对象或服务)之间共享的公共类型提供了一种潜在的可能性,即内部实现的复杂性可以从使用它的应用程序的其余部分中分离出来。在这里,常见的类型抽象了一个数的表示形式的简单选择。一般来说,每个实现选项都会更复杂、更有趣。

4.【Object subtyping(类似于C++中类的继承与派生)】

In Motoko, objects have types that may relate by subtyping, as the various types of counters do above. As is standard, types with more fields are less general (are subtypes of) types with fewer fields. For instance, we can summarize the types given in the examples above as being related in the following subtyping order:

  • Most general:
{ bump : () -> Nat }
  • Middle generality:
{
   inc  : () -> () ;
   read : () -> Nat ;
   bump : () -> Nat ;
 }
  • Least generality:
{
   inc  : () -> () ;
   read : () -> Nat ;
   bump : () -> Nat ;
   write : Nat -> () ;
 }

If a function expects to receive an object of the first type ({ bump: () → Nat }), any of the types given above will suffice, since they are each equal to, or a subtype of, this (most general) type.

However, if a function expects to receive an object of the last, least general type, the other two will not suffice, since they each lack the needed write operation, to which this function rightfully expects to have access.

5.【Object classes】

In Motoko, an object encapsulates state, and an object class is a package of two entities that share a common name.

Consider this example class for counters that start at zero:

class Counter() {
   var c = 0;
   public func inc() : Nat {
     c += 1;
     return c;
   }
 };

即以后Counter返回一个新创建的对象,实例为……

The value of this definition is that we can construct new counters, each starting with their own unique state, initially at zero:

let c1 = Counter();
let c2 = Counter();

Each is independent:

let x = c1.inc();
let y = c2.inc();
(x, y)

We could achieve the same results by writing a function that returns an object:

func Counter() : { inc : () -> Nat } =
   object {
     var c = 0;
     public func inc() : Nat { c += 1; c }
   };

理解:以上代码声明了一个名为Counter的函数,该函数形参为空,返回值类型为{inc : () -> Nat},即返回值是object对象,且=号后面给出了该返回值类型(object)的实例(实现),为object{var c=0; public func inc() : Nat{c+=1; c } };


⚠️语法补充:直接给出实现 等同于 给出type,然后加上等于号,后面给出实现。

例:

class Bits(n : Nat) {
    var state = n;
    public func next() : ?Bool {
        if (state == 0) { return null };
        let prev = state;
        state /= 2;
        ?(state * 2 != prev)    //类型为?Bool
    }
}

等同于

type Bits = {next : () -> ?Bool}
let Bits : Nat -> Bits =
func Bits(n : Nat) : Bits = object {
    // class body
};

Notice the return type of this constructor function (an object type):

{ inc : () -> Nat }

We may want to name this type, for example, Counter, as follows, for use in further type declarations:

type Counter = { inc : () -> Nat };

即:可以将一个object的type命名,以便日后快速使用,减少码字量,并便于维护。

In fact, the class keyword syntax shown above is nothing but a shorthand for these two definitions for Counter: a factory function Counter that constructs objects, and the type Counter of these objects. Classes do not provide any new functionality beyond this convenience.

即:实际上,class关键字就是一个函数(如type为func:()->Counter),这个函数实现了以上两个模块的简写,一是声明了Counter是 返回值type为object type{…}(该type起名为Counter) 的一个函数,二是给出了该type的具体实现。(真方便……)

6.【Class constructor】

对象类定义了一个构造函数,该构造函数可以携带零个或多个数据参数(data argument)和零个或多个类型参数(type argument)。

Data arguments

Suppose we want to initialize the counter with some non-zero value. We can supply that value as a data argument to the class constructor:

class Counter(init : Nat) {
   var c = init;
   public func inc() : Nat { c += 1; c };
 };
结果:func : Nat -> Counter

This parameter is available to all methods.

For instance, we can reset the Counter to its initial value, a parameter:

class Counter(init : Nat) {
   var c = init;
   public func inc() : Nat { c += 1; c };
   public func reset() { c := init };
 };

Type arguments

假设我们想让计数器实际携带它计数的数据(就像一个专门的缓冲区)。

当类使用或包含任意类型的数据时,它们会为该未知类型携带一个类型参数(或等效的类型参数),就像函数一样。

与数据参数一样,此类型参数的作用域覆盖整个类。因此,类的方法可以使用(并且不需要重新引入)这些类型参数。

import Buffer "mo:base/Buffer"; 
class Counter<X>(init : Buffer.Buffer<X>) { 
    var buffer = init.clone(); 
    public func add(x : X) : Nat { 
        buffer.add(x); 
        buffer.size() 
    }; 
    public func reset() { buffer := init.clone() }; 
};
结果:func : <X>(Buffer<X>) -> Counter/1<X>

Type annotation

类构造函数可以为其“返回类型”(它生成的对象类型)携带类型注释。提供时,Motoko检查此类型注释是否与类的主体(对象定义/实现)兼容。此检查确保构造函数生成的每个对象都符合提供的规范。

For example, we repeat the Counter as a buffer, and annotate it with a more general type Accum<X> that permits adding, but not resetting the counter. This annotation ensures that the objects are compatible with the type Accum<X>.

import Buffer "mo:base/Buffer"; 
type Accum<X> = { add : X -> Nat }; 
class Counter<X>(init : Buffer.Buffer<X>) : Accum<X> { 
    var buffer = init.clone(); 
    public func add(x : X) : Nat { 
        buffer.add(x); 
        buffer.size() 
    }; 
    public func reset() { buffer := init.clone() }; 
};

Full syntax:

In full, classes are defined by the keyword class, followed by: - a name for the constructor and type being defined (for example, Counter) - optional type arguments (for example, omitted, or <X>, or <X, Y>) - an argument list (for example, (), or (init : Nat), etc.) - an optional type annotation for the constructed objects (for example, omitted, or Accum<X>), - the class "body" is an object definition, parameterized by the type and value arguments (if any).

即:在全文中,类由关键字class定义,后跟:-要定义的构造函数和类型的名称(例如,Counter)-可选类型参数(例如,省略,或<X>,或<X,Y>)-参数列表(例如,(),或(init:Nat)等)-构造对象的可选类型注释(例如,省略,类“body”是一个对象定义,由类型和值参数(如果有的话)参数化。

The constituents of the body marked public contribute to the resulting objects' type and these types compared against the (optional) annotation, if given.

即我上面说的,私有数据不会体现在type中。

7.【Structural subtyping

Motoko中的对象子类型使用结构子类型,而不是名义子类型。

回想一下,在命名类型中,两种类型的相等性问题取决于选择一致的、全局唯一的类型名(跨项目和时间)。

而在Motoko中,两种类型的相等性问题是基于它们的结构,而不是它们的名称。

由于结构类型的原因,命名类类型提供了一个方便的缩写。

但是,出于类型化的目的,最重要的是相应对象类型的结构:两个名称不同但定义相同的类生成类型兼容的对象。(即:type相同,就满足类型兼容原则,不需要名字之间有什么关系)

在类声明中提供可选类型注释时,将检查一致性:对象类型必须是注释的子类型。然而,注释并不影响类的类型,即使它只描述了对象类型的一个适当的超类型。

形式上,Motoko中的子类型关系扩展到所有类型,而不仅仅是对象类型。

大多数情况下是标准的,并遵循传统的编程语言理论(特别是对于结构子类型)。

对于新程序员来说,Motoko中其他值得注意的例子包括array, options, variants and number type inter-relationships(数组、选项、变体和数字类型的相互关系)。

2021050309181944-1024x535.png

Dfinity


A Student on the way to full stack of Web3.