Linda¶
用于协调不同的程序,使它们进行协作。
提醒
在 Adapter >= 4.0 中可用。
介绍¶
CodeLab Adapter 4.0 内置了 Linda server(Tuple Space),目前我们提供了以下客户端(持续增加中...)与 Linda Tuple Space 交互:
Linda 最有趣的一个地方是,所有 Tuple Space 参与者(跨语言、跨系统、跨网络)都能够互操作,语义由参与者自己"协调", 所以 Alan Kay 将 Linda 称为"协调语言"。
基本操作(operate)¶
核心操作¶
- out: 生成一个元组(tuple) 到 元组空间(tuple space)
- in: 在tuple space中匹配元组,如果匹配到则消耗它, 如果未匹配则一直等待
- inp: in的非阻塞版本。 如果匹配到则消耗它, 如果未匹配则返回空元组
- rd: read only, 在tuple space中匹配元组,如果匹配到则返回它(不移除), 如果未匹配则一直等待
- rdp: 非阻塞版本的 rd
- eval: 暂不考虑实现
辅助操作¶
不在 linda 的原始论文中,是我自己的扩展
- dump: 获取元组空间所有元组
- status: 获取元组空间状态
- reboot: 重置元组空间
- reboot 将重置元组空间,确保执行这个操作时,没有任何其他 linda client ,否则,此前的 linda 操作将一直处于等待中。
Python Client¶
提醒
已经内置在完整版(Windows/macOS)里, 打开 Jupyterlab 即可使用。
安装依赖: pip install codelab_adapter_client
提供同步和异步两种基类:
- AdapterNode
- AdapterNodeAio
AdapterNode¶
import time
from codelab_adapter_client import AdapterNode
class MyNode(AdapterNode):
NODE_ID = "linda/test"
def __init__(self):
super().__init__()
node = MyNode()
node.receive_loop_as_thread()
time.sleep(0.1) # 等待zmq通信管道建立完成
创建Adapter Node之后,就可以通过node使用linda了。
res = node.linda_reboot() # reboot linda server, clean tuple space
assert res == []
res = node.linda_out([1, 2, 3]) # out
assert res == [1, 2, 3]
res = node.linda_out([1, 2, 4]) # out
res = node.linda_dump()
assert res == [[1, 2, 3], [1, 2, 4]]
res = node.linda_rd([1, 2, 3]) # read and blocking
assert res == [1, 2, 3]
res = node.linda_rdp([1, 2, "*"]) # read but non-blocking
assert res == [1, 2, 3] # 先入先出
res = node.linda_in([1,2,3]) # read then remove (blocking)
assert res == [1, 2, 3]
AdapterNodeAio(异步)¶
同步和异步 API 保持一致
import asyncio
from codelab_adapter_client import AdapterNodeAio
class MyNode(AdapterNodeAio):
NODE_ID = "linda/test"
def __init__(self):
super().__init__()
# 以下代码在 jupyter 中运行,如果你想在python脚本中使用,请考虑异步代码的生命周期,参考: https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/tests/test_linda_client.py#L26
node = MyNode()
task = asyncio.create_task(node.receive_loop())
await asyncio.sleep(0.1) # !! 等待zmq通信管道建立完成
_tuple = ["test_linda"]
# reboot
res = await node.linda_reboot()
assert res == []
# out
_tuple = ["hello", "world"]
await node.linda_out(_tuple)
# rdp
res = await node.linda_rdp(_tuple)
assert res == _tuple
# inp
res = await node.linda_inp(_tuple)
assert res == _tuple
res = await node.linda_dump()
assert res == []
更多用法参考测试文件: test_linda_client.py
Scratch Client¶
REST API¶
使用 httpie 作为客户端。:=
表示后边跟的是 json 数据
out¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=out tuple:='["hello", "linda"]'
in¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=in tuple:='["hello", "linda"]'
dump¶
http post https://codelab-adapter.codelab.club:12358/api/linda operate=dump
其他原语类似
cli (命令行客户端)¶
pip install https://github.com/CodeLabClub/codelab_adapter_client_python/archive/master.zip
# pip install codelab_adapter_client --upgrade # 暂未更新到 pypi
codelab-linda --help
codelab-linda out --help
# reboot
codelab-linda reboot
# dump
codelab-linda dump
# out
codelab-linda out --data '[1, "hello"]'
# rd
codelab-linda rd --data '[1, "hello"]'
codelab-linda rd --data '[1, "*"]'
# rdp
codelab-linda rd --data '[1, 2, 3]' # []
# in
codelab-linda rd --data '[1, "*"]'
JavaScript Client¶
方便开发者,将 Linda 引入自己的web项目。
CodeLab 目前使用 JavaScript Client,将 Linda 带入 CodeLab Scratch、CodeLab Adapter WebUI 和 Lively。
import AdapterBaseClient from "./codelab_adapter_base.js"; // https://github.com/CodeLabClub/scratch3_eim/blob/v3/codelab_adapter_base.js
let NODE_ID = "linda/js/client";
let HELP_URL = "https://adapter.codelab.club/user_guide/Linda/";
let runtime = null;
let adapter_client = new AdapterBaseClient(NODE_ID, HELP_URL, runtime);
await adapter_client.linda_out([1,2,3]).then((data)=>{console.log("linda",data); return data})
tuple = await adapter_client.linda_in(["hi", "lively", "*"]).then((data)=>{console.log("linda",data); return data})
tuple = await adapter_client.linda_in(["hi", "python", "from Lively"]).then((data)=>{console.log("linda",data); return data})
tuple = await adapter_client.linda_in(["hello", "lively", "*"]).then((data)=>{console.log("linda",data); return data})
await adapter_client.linda_in([1,2,5], 1000).then((data)=>{console.log("linda",data); return data}) //超时
mush-lang¶
LISP 是一种构建材料 -- Alan Kay
为了更好地探索 Linda 的可能性,我们围绕 Linda 的基本原语,构建了一门简单的语言 -- mush-lang。
mush-lang 采用 LISP 风格的语法,可以视为 LISP 的一门玩具方言。 LISP 因其同构性(内外表示一致),可能是所有语言中最简单的。
mush-lang 目前在 Python 中实现。
Demo¶
多个 Scratch 角色 的 实时同步¶
在 Python 的例子中,我们甚至在Scratch里构建了 Server!
两个Scratch角色同步的代码如下
python 与 Scratch 同步的代码如下:
Python核心部分代码为:
node.linda_out(["request", "loudness", "xxx"])
node.linda_in(["response", "loudness", "*"])
Jupyter 与 Scratch 的互操作¶
跨语言对象之间的互操作
用到了 jupyterlab 3.0 里的 ipywidgets.
# 在jupyterlab 3.0中可用
from ipywidgets import interact, interactive, fixed, interact_manual
from codelab_adapter_client import AdapterNode
import time
class MyNode(AdapterNode):
NODE_ID = "linda/jupyter"
def __init__(self):
super().__init__()
node = MyNode()
node.receive_loop_as_thread()
@interact(show=True, x=100, size=100)
def f(show,x,size):
node.linda_out(["%%x", x], wait=False) # f函数是非阻塞的回调函数,使用wait=False参数,使node.linda_out使非阻塞的,此时相当于流,记得使用 message tuple(见下文)
node.linda_out(["%%show", show], wait=False)
node.linda_out(["%%size", size], wait=False)
return show,x,size
进阶¶
消息风格¶
linda 的基本观点是数据不停生灭(由用户显式操控)。
如果我们想在 Linda 中实现 "消息/流" 的模式,可能会遇到tuple堆积(生产者太快)的问题(这是很严重的问题,似乎也不是正确使用linda的方式)
为了尽可能少地破坏概念完整性,我们引入了一种特殊的tuple来支持"消息/流"模式。
我们定义了一种叫做 message tuple 的 tuple,它像消息一样,每次只能流的瞬时截面: 一个数据。
以下是几个message tuple的例子:
- ("%%x", 1)
- ("%%y", 50)
- ("%%z", "hello", "world")
在语法层面,message tuple只是普通的tuple,唯一区别是第一个元素需要是如下风格字符串, "%%x", x可以是任意值,可以把它看作message tuple的id,不同id的message tuple被视为不同tuple,支持tuple的所有操作符。
以下是一个例子:
视角
站在变量的视角,你可以将其看作全局变量
FAQ¶
如何看到 Linda Tuple Space¶
Adapter >=4.1.0
在 Scratch 里有些 in/rd 积木一直阻塞¶
简单而言,按照以下顺序运行程序:
- 确保在linda in/rd 积木运行之前,先运行linda reboot
- 之后在启动Scratch程序
以下是原因分析(可以不看):
这个Linda背后的实现有关,Adapter Linda 目前是C/S架构。Scratch中的 in/rd 积木实际上 promise。
reboot针对的是linda server的操作。
如果程序在 in/rd 的时候,被reboot,则客户端(Scratch)的 in/rd 对应的promise永远不会被解决。
linda reboot 一下
速度¶
默认情况下,30帧/s。
在Python客户端,通过修改参数,可以提高到300-600帧/s。
class MyNode(AdapterNode):
NODE_ID = "linda/test"
def __init__(self):
super().__init__(recv_mode="block", bucket_fill_rate=1000, bucket_token=1000)
Linda 与 EIM¶
Linda 与 EIM 将长期共存,一个 Adapter Node,即是Linda client,也是EIM client,它们各有所擅。长期来看,我们更偏好 Linda。