回想一下,在 Motoko 中,可变(mutable)的状态对于actor来说总是私有(private)的。
但是,两个actor可以共享消息数据,这些消息可以引用(refer to)actor,包括他们自己和彼此。此外,如果这些函数是共享(shared)的,消息可以引用(refer to)单个函数。
通过这些机制,两个actor可以通过异步(asynchronous)消息传递来协调它们的行为。
带有actor的Publisher-subscriber模式:
本节中的示例通过关注publish-subscribe模式的变体,演示actor如何共享其功能。在Publisher-subscriber模式中,一个publishing actor记录subscriber actor列表,以便在publisher的状态中发生值得注意的事情时进行通知。例如,如果publisher actor发布了一篇新文章,则会通知subscriber actor有一篇新文章可用。
下面的示例使用 Motoko 中的两个actor来构建Publisher-subscriber关系的变体。
PS:要查看使用此模式的工作项目的完整代码,请参阅示例存储库中的 pubsub 示例。
Subscriber actor:
下面的 Subscriber actor类型提供了一个可能的接口,以便subscriber actor和publisher actor分别公开和调用:
type Subscriber = actor {
notify : () -> ()
};
- Publisher使用此类型定义数据结构以将其Subscribers存储为数据。
- 每个Subscriber actor公开一个 notify update 函数,如上面的Subscriber actor类型签名中所述。
注意,子类型(sub-typing)允许Subscriber actor包含此类型定义中未列出的其他方法。
为简单起见,假设 notify 函数接受相关的通知数据,并向Publisher返回一些关于Subscriber的新状态消息。例如,Subscriber可能根据通知数据返回对其订阅设置的更改。
Publisher actor:
Publisher端的代码存储Subscribers数组。为简单起见,假设每个Subscriber只使用一个subscribe函数订阅自己一次。
import Array "mo:base/Array";
actor Publisher {
var subs: [Subscriber] = [];
public func subscribe(sub: Subscriber) {
subs := Array.append<Subscriber>(subs, [sub]);
};
public func publish() {
for (sub in subs.vals()) {
sub.notify();
};
};
};
稍后,当某个未指定的外部代理调用publish函数时,所有Subscribers都会收到通知消息,如上面给出的Subscriber类型中所定义的。
Subscriber methods:
在最简单的情况下,Subscriber actor具有以下方法:
- 使用 init 方法订阅来自Publisher的通知。
- 按照上面给出的Subscriber类型中的 notify 函数指定的方式,作为其中一个subscribed actor接收通知。
- 允许对累积状态的查询,在这个示例代码中,它只是一个get方法,用于获取接收到的通知和存储在 count 变量中的通知数。
下面的代码说明了如何实现这些方法:
actor Subscriber {
var count: Nat = 0;
public func init() {
Publisher.subscribe(Subscriber);
};
public func notify() {
count += 1;
};
public func get() : async Nat {
count
};
}
actor假设只调用其 init 函数一次,但并不强制执行这个函数。在 init 函数中,Subscriber actor传递一个类型为 actor { notify: ()→()} ;
(上面在本地,名为Subscriber
)的自身的引用。
如果多次调用,actor将多次订阅自己,并从Publisher那里接收多个(重复的)通知。这种脆弱性是我们上面展示的基本Publisher-subscriber模式的结果。如果更加小心,更高级的Publisher actor可以检查重复的Subscriber actor,并忽略他们。
Sharing functions among actors:
在 Motoko 中,共享的actor函数可以在消息中发送给另一个actor,然后由该actor或另一个actor调用。
为了说明起见,对上面显示的代码进行了简化。完整版为Publisher-subscriber关系提供了额外的特性,并使用共享函数使这种关系更加灵活。
例如,通知函数总是被指定为 notify。一个更加灵活的设计将仅仅固定通知的类型,并允许Subscriber选择其共享函数中的任何一个,这些函数在subscribe消息中指定,以代替仅仅包含订阅的actor的消息。
PS:有关详细信息,请参阅完整示例。
特别是,假设Subscriber希望避免被锁定到其接口的某个命名方案中。真正重要的是,Publisher可以调用用户选择的某个函数。
shared关键字:
为了允许这种灵活性,actor需要共享一个允许从另一个actor进行远程调用的函数,而不仅仅是对自身的引用。
共享一个函数的能力要求它被预先指定为共享(shared),类型系统强制这些函数遵循这些函数接受、返回和关闭闭包的数据类型的特定规则。
Motoko 允许您省略public actor方法的这个关键字,因为无论是否显式标记,public actor的任何public函数都必须被“共享”。
使用共享函数类型,我们可以扩展上面的例子,使其更加灵活。例如:
type SubscribeMessage = { callback: shared () -> (); };
这个类型与原始类型的不同之处在于,它描述了一个消息记录类型,只有一个名为 callback的字段,上面首先显示的原始类型描述了一个只含有一个名为 notify的方法的actor类型:
type Subscriber = actor { notify : () -> () };
值得注意的是,actor 关键字意味着后一种类型不是包含字段的普通记录,而是包含至少一个“必须名为notify的方法”的 actor。
通过使用 SubscribeMessage 类型,Subscriber actor可以为其 notify方法选择另一个名称:
actor Subscriber {
var count: Nat = 0;
public func init() {
Publisher.subscribe({callback = incr;});
};
public func incr() {
count += 1;
};
public query func get(): async Nat {
count
};
};
与原始版本相比,唯一更改的行是使用表达式{ callback = incr;}
将 notify 重命名为 incr 并形成新订阅消息有效负载的行。(第四行)
同样地,我们可以更新Publisher以获得一个匹配的接口:
import Array "mo:base/Array";
actor Publisher {
var subs: [SubscribeMessage] = [];
public func subscribe(sub: SubscribeMessage) {
subs := Array.append<SubscribeMessage>(subs, [sub]);
};
public func publish() {
for (sub in subs.vals()) {
sub.callback();
};
};
};
Comments 1 条评论
博客作者 Dana Searls
I’ve recently started a web site, the information you offer on this website has helped me greatly. Thanks for all of your time & work.