制造伟大的、可成长的系统的关键是设计出模块之间的沟通机制,而不是关于内部属性和行为应该是什么。 – Alan Kay

前言

Snap!JavaScript function 设计得极为出色, 仅通过一个积木就将浏览器的所有能力带给了 Snap! 。 它给了我巨大冲击。 关于这点我们在 Snap! 使用笔记: JavaScript function 做了讨论。

目标

为了将 Snap! 用作日常计算/编程环境,需要突破浏览器的限制,使其得到操作系统的所有能力。我打算沿着Jens Mönig(Snap! 创始人)的设计思路, 来做这件事。

先前的工作

之前我基于 CodeLab Adapter 的消息系统为 Snap! 接入 Python 来达成这个目标。想法是: 如果我们能够完成 Snap! 与 Python 的互操作,就能在 Snap! 中完成任何工作, 因为 Python 解释器封装了底层操作系统的所有接口(Python 通常被认为是更好的 Bash), 如此一来我们就可以在 Snap! 里进行 系统 io 编程、管理远程服务器、渗透测试、与其他进程互操作…

以下是一些我渴望在 Snap! 中获得的系统能力:

  • 处理文件/文件夹/路径
    • Python 库: pathlib
    • 以 GUI 的方式打开文件/软件/目录: from codelab_adapter.utils import open_path_in_system_file_manager
  • 调用命令行工具
    • Python 库: subprocess、sh
  • 与网络交互
  • 接管鼠标键盘

由于接入了 Python, 我们不仅得到了与浏览器之外的系统交互的能力, 还得到了 Python 的巨大生态库:

  • 计算机视觉
    • OpenCV
  • 神经网络
    • PyTorch、TensorFlow
  • 各类机器人驱动库…

最初我们在 CodeLab Scratch 里实现了这些想法, 它体现为 Python 插件:

我最近将其移植到了 Snap! 中:

所以它的使用方式和最初的文档记录的使用方式,完全一致:

重构

先前的插件存在一个问题: 不够 lively。 它类似于Snap! 使用笔记: JavaScript function里提到的使用 primitive 扩展 Snap! : 需要先在 Jupyterlab 中编写 Python 代码, 然后重启 extension_python 插件, 之后在 Snap! 里调用它, 虽然无需刷新 Snap! 页面,但切换到 Jupyterlab、重启插件, 依然打断我的思路。

由于受到 JavaScript function 的强烈冲击, 我打算以类似的方式来做这件事: 将操作系统的所有能力带给 Snap! (通过与 Python 互操作)。

Python function 插件

这些想法的最终产物是 Python function 插件:

Python function 并没有设计成单个积木, 而是设计成了一个插件, 里边多了一些辅助类积木。

怎么用?

为了使用 Python function 积木, 你需要做 3 件事(与JavaScript function一致):

  1. 打开权限
    1.1. 开启 CodeLab Adapater
    1.2. 开启 extension_python 插件
    1.3. 设置 token(出于安全考虑)
  2. 编写 Python 函数
  3. 调用 Python 函数

1. 打开权限

  • 1.1. 开启 CodeLab Adapater

下载最新的 CodeLab Adapater, 并运行它。

CodeLab Adapater 正常运行之后,你会在 CodeLab Snap! 页面里看到这个绿色的圆点(和 CodeLab Scratch 类似)

如果 CodeLab Adapater 没有启动或出了问题,这个原点就会是灰色的。

  • 1.2. 开启 extension_python 插件

  • 1.3. 设置 token (出于安全考虑)

2. 编写 Python 函数

Python function 插件中找到 Python function 积木。上图编写了一个匿名函数, 参数是 path, 函数体是打开它(可能是目录、文件或系统里的 GUI 软件)。

附上相应 Python 代码

1
2
from codelab_adapter.utils import open_path_in_system_file_manager
open_path_in_system_file_manager(path)

3. 调用 Python 函数

接下来我们就可以调用它。

JavaScript function 一样, 有两个积木可用于执行 JavaScript function:

  • 运行(没有返回值)
  • 调用(有返回值)

这两个积木都内置在 Python function 插件里(底层实现与控制组里的同名积木并不相同)

更复杂的例子, 可以右键查看examples积木的定义:

我的用例

Snap! 使用笔记: JavaScript function 提到的用例类似。

我不仅使用 Python function 来增强 Snap!, 也借助 Snap! 的个人计算环境来探索 Python 以及背后的操作系统。

此外有一个有趣的用例是, 使用 Python function 来执行 Codification 示例自动生成的 Python 代码:

一些技巧

安装第三方库

我喜欢使用 sh 库来在 Python 里使用 linux 工具箱。

CodeLab Adapter 没有内置这会库, 但你可以自行安装它:

安装完成之后,就可以使用它了:

按行分割,将它们转化为列表:

由于 Snap! 从 Lisp 那里继承了处理列表的强大能力, 又从 Smalltalk 那里继承了 liveness 的特质, 现在你就可以以 lively 方式随意摆弄它们了!

类型转化

从 Snap! 中调用 Python 函数时, Snap! 的数据类型会自动转化为 Python 类型, 随意你可以随意使用内置积木调用 Python 函数。

从 Python 中返回的简单数据类型(字符串、数字), 会自动转化为 Snap! 数据类型. 当你想将复杂数据结构 return 到 Snap! 里时,建议使用 json 序列化你的数据(examples积木里有例子), 然后在 Snap 中处理 json 即可。

全局变量

这些匿名函数转瞬即逝,有时,你希望让计算的结果贮存在全局空间,可以在不同函数里读写它

这使得许多事情变得简单

已知限制

extension_python 以线程的方式启动, 有一些库需要在独立进程里启动(非常罕见)。如果你遇到这类问题,可以发邮件跟我讨论。

由于 extension_python 是开源的,你可以自行修补它,使用一些 hack 技巧,总是可以绕过限制。

我目前还没有遇到过这些限制。

编程风格

尽可能少地引入 primitive 是 Smalltalk 社区编写 primitive 遵循的风格, 关于 primitive 的 big ideas, 我在 MicroBlocks 与 Snap!的互操作里(视频)提到过一些 – Snap! 使用笔记: JavaScript function

当我在 Snap! 中使用 Python 时,我也遵循类似的风格, 尽可能少地引入底层的能力(除非必要), 尽可能多地使用 Snap! 积木来编程。

我希望涉及 Python 的部分足够小, 一般而言是为了补充浏览器没有的能力(本地系统功能、网络),我将多数的计算过程放在 Snap! 里,借助 Snap! 这样的个人计算环境,我可以直观触摸到数据,以及充分利用个人计算环境的其他杠杆力量。

未来

Python function 的 Python 代码,最终运行在 CodeLab Adapater 内置的 Python 解释器里。未来我们也可能支持基于 WebAssembly 的 Python 解释器(如Pyodide)。

后记

递归是我之前最恐惧的领域之一,但在 Snap! 里玩耍它几乎是一种消遣。 因为你可以"看见"并把玩程序与数据结构。

这一段代码,使用递归将 Snap! 数据类型递归转化为 Python 数据类型, 它可以处理任意深度的 Snap! 列表。

参考