人的心智活动透过简单的概念而发挥它的力量,方法主要可分为三种:第一,把数个简单的概念组合成一个复合的概念,于是所有复杂的概念成焉。第二,将两个概念,不论简单或复杂,不将它们结合,而是同时并列在一起观察,如此就能得知何为相互关联。第三,把某些概念,与伴随它们其他真实存在的概念区隔出来,称为这抽象艺术化,所有一般化,概化的概念皆是由此而生 - 约翰·洛克《人类理解论》

**提醒: 本文的论断不适用于 MicroBlocks, MicroBlocks 在这个领域取得了许多根本性的进步 **

scratch3.0 + micro:bit中,我们提到

在少儿编程/硬件编程教育领域,硬件编程有两种风格,我将这两种风格分别称为灌入式交互式

我相信就编程教育而言,交互式优于灌入式

这篇文章我们将讨论这两种编程风格给学习者的编程体验 以及心理状态所带来的影响。所谈论的很多内容,在编程语言的发展历史中都被反复讨论过。

本文中,我们只讨论图形化硬件编程,但得出的结论并不局限于此

灌入式

灌入式阵营有名的图形化项目包括:

事实上,几乎所有传统的硬件编程都是灌入式的

编程方式

我们以makecode microbit为例,来展示灌入式编程的编程方式.

makecode microbit中,通过拖拽积木,拼搭出我们的程序,接着将程序下载到本地(程序在线上完成编译),最后将下载的文件拖入micro:bit,即可运行。

将代码/固件灌入硬件中,代码(代码编译出的固件)将在硬件运行。我将这种编程风格称为灌入式

如果你熟悉blockly,你会发现这是一种典型的blockly风格(当然,blockly也可以写出交互式风格的blockly app)

交互式

交互式阵营的图形化编程项目有:

编程方式

我们以cozmo code lab为例,来展示交互式编程的编程方式.

cozmo code lab中,你同样身处积木化的编程界面里,通过拖拽积木,拼搭出所需的程序,点击运行,cozmo即可按照程序的逻辑运行。

程序运行在ipad/手机上,每个积木在实际执行的时候,将消息发给cozmo,从而控制cozmo,这是我将这种风格程序成为交互式的原因

cozmo code lab中一个非常惊艳的编程体验是:如果你不知道一个积木有什么作用,你不必去翻说明书,你只需要点击一下这个积木,cozmo立马执行这个积木的逻辑,你可以直观地理解陌生的积木。交互式编程所具有的及时反馈特性,鼓励学习者去探索。这是交互式编程给学习者心理上带来的影响之一。

建构主义者(如皮亚杰、艾伦凯、派普特)应该会喜欢这种风格

如果你熟悉scratch,你会发现这是一种典型的scratch风格(对象响应消息,很Smalltalk)。关于scratch风格和blockly风格的比较可以参考我之前的文章:Blockly与Scratch3.0的比较分析及选型建议

对比分析

真理越辩越明

在此我们来对比分析一下灌入式交互式各自的优势

灌入式阵营可能列出的优势有:

  1. 灌入式可以离线运行,只需要将代码烧入进去,即可脱离编程工具
  2. 灌入式将带来优于交互式的实时性
  3. 灌入式因为需要生成(generate)代码,学生可以查看代码

如果有遗漏,欢迎拥护灌入式编程的小伙伴来信(wuwenjie718@gmail.com)补充

由于我拥护交互式编程,所以我准备在下文里,反驳灌入式的优势,并指出交互式所具有的优势。如果你不赞同,欢迎来信反驳,观点合理的话,我会及时更新到本文,如有必要,我们也可以使用理性辩论的平台 Kialo来进行辩论

反驳灌入式的优势

我们针对上边灌入式阵营提出的三条优势,逐条反驳

灌入式可以离线运行,只需要将代码烧入进去,即可脱离编程工具

这个论述本身并没有需要反驳的地方,它只是陈述了一个事实,在此我想提出的是,对于教育而言,离线运行不是很重要的特性,如果是工业级的项目或者解决具体问题的硬件产品,离线运行可能是重要的,但对于教育项目而言,我认为这个特性并不重要,如果你实在不愿意一直开着电脑,你可以把上位机运行在树莓派里,若嫌贵,可以使用荔枝派(只要9块9哦)。但需要提醒的是,为了得到离线运行的特性,我们很可能会失去交互性(这个问题不是很好讨论,如果需要,我们之后专门来说说这两个特性在什么情况可能冲突,而不可兼得)。

灌入式将带来优于交互式的实时性

和上一条观点一样,我认为严格的实时性在编程教育中并不重要,交互式理论上足以提供毫秒级别的实时性,更高的实时性在教育中是否必要,我持怀疑态度。我将离线运行实时性视为一种对机器性能的优化,前者节约资源、后者节约时间,但这种节约下来的时间,短得也许人类不能感知。我承认在一些竞技类的比赛中,实时性是至关重要的,但在教育中,尤其是低龄化编程教育中,被教育者是这件事的核心,我认为这些特性并不重要,尤其是考虑到这些特性可能和交互性冲突

灌入式因为需要生成代码,学生可以查看代码

给用户呈现积木所对应的代码,是个帮助用户从图形化过度到文本编程的好办法. 但生成代码这件事并不限于灌入式,交互式的编程界面里,虽然你并不需要生成代码,但如果愿意,完全可以为每个积木生成对应的代码,而且可以是任何语言,任何抽象粒度灌入式阵营的小伙伴可能会反驳说,你们生成的代码并不是真正用于运行的。我会回答说: 对,我恰恰认为,不该把真正执行的代码生成出来给用户看。积木之所以是个好工具,正是因为它能自如地隐藏复杂度,暴露出合适粒度的概念颗粒,积木并不只是帮助我们省去记忆语法规则,更重要的是,它允许我们根据学生所处的阶段,给予他不同抽象程度的积木。我很喜欢来自lisp社区的忠告:表达你的意图,而不是操作过程,这样有助于我们能站在更高的抽象层面上

关于这一点,code.org给了我们很好的示范,一个学习者,在code.org里在拖拽了两个积木之后,他看到页面里愤怒的小鸟往东飞一步,接着又往北飞一步,最后成功击中了小猪。

如果学习者愿意,他可以看看与积木等价的代码:

我们看到,这些代码隐藏了很多实现的细节,你也许要抱怨说code.org在欺骗学习者,这并不是真正运行的代码,真正的代码是由js在操控svg的元素,但你确定你要给出这个粒度的东西吗?真正的代码也许是一串001101101.... 我认为平台给出合适抽象粒度的积木在编程教育里是至关重要的

就这点而言,目前很多灌入式的图形化编程并不适合编程教育,尤其不适合少儿编程,他们直接把驱动硬件代码包装到积木下就了事了,积木颗粒在抽象程度上与硬件文本编程无异。如何设计出合适抽象粒度的积木块,不是个简单的问题,我认为这块的从业者都该多看看cozmo和makecode

交互式的优势

反驳完灌入式的优势,接着我们来谈谈交互式的优势何在,我先简单列出,之后逐条陈述

  1. 及时的反馈
  2. 允许单步调试
  3. 软件编程和硬件编程,不必区分,虚拟人物与现实硬件能彼此联动
  4. 强大的可扩展性

罗列完交互式的优势后,我们来逐条陈述它们.

及时的反馈

我认为这一条,对编程教育是至关重要的。

我们从REPL(Read-Eval-Print-Loop)说起,LISP最早为我们带来REPL。REPL是个交互式的编程环境,用户输入的表达式及时被求值运行,并输出,这对于学习一门新的编程语言有很大的帮助,因为它能立刻对初学者做出回应,所以这个概念被移植到很多编程语言环境里(Python, Scala , Java(since jdk-9)…)。这种交互式的编程环境使得探索性的编程和调试更加便捷,因为“读取-求值-输出”循环通常会比经典的“编辑-编译-运行-调试”模式要更快,这两者的差异很像交互式灌入式的差异。我自己使用REPL的一个强烈感受是,在REPL环境中(如ipython),我更乐于探索,对于不懂的api,我会直接做个实验,看看效果,获得直观感受。REPL鼓励每个人在探索中成为自己知识的构建者。而这点正式是早期致力于推广少儿编程的先驱们(艾伦凯、派普特)所追求的

我将硬件编程里的交互式编程方式,视为一种硬件编程的REPL。如同我在前头举例说的

cozmo code lab中一个非常惊艳的编程体验是:如果你不知道一个积木有什么作用,你不必去翻说明书,你只需要点击一下这个积木,cozmo立马执行这个积木的逻辑,你可以直观地理解陌生的积木。交互式编程所具有的及时反馈特性,鼓励学习者去探索系统。这是交互式编程给学习者心理上带来的影响之一。

对教学和平台而言,及时的反馈也能带来诸多好处,有了来自硬件的反馈信息,我们可以在平台中给出更多的提示信息,来指导学生修正错误,或者引导他抵达目标,目前国外社区已经有这块的试水者,我正在关注,之后有机会专门写一篇文章。

— 2019.01.26更新

Bret Victor在Inventing on Principle支持了这个观点。及时的反馈对于创造是非常重要的。

“创造者需要即时的反馈”

“如果你无法即时看到东西,就无法找到你要的灵感”

“你应该看到你做出的改变对系统的即时影响”

“想法需要一个环境,创作者可以培育它们”

允许单步调试

如果学习者写了一段较为复杂的代码,运行时没有达到预期效果,人们往往无法通过阅读代码找出问题(错误往往揭示了知识或思维盲区),它需要逐行运行代码,看看效果,然后判断究竟是哪一步出了问题,这就是单步调试之所以如此重要的原因,即便对于专业程序员,也是如此。在灌入式中我们无法做到单步调试,因为代码是一股脑灌入硬件的。但交互式允许我们这样做,因为每次都是消息通信,所以编程界面可以逐步地给硬件发送控制指令。如果你观察过学生在code.org中通过单步调试找到问题,并顺利前行,你就知道这个特性有多棒

软件编程和硬件编程,不必区分,虚拟角色与现实硬件能彼此联动

在图形化编程这块,学习者很可能是先学了软件编程,通过闯关式的学习,利用积木控制虚拟角色来达到目标,在这个过程中掌握编程概念。交互式硬件编程允许你把硬件也接入到这些web平台里。如此一来,无论是教学软件还是硬件,平台架构上将毫无差别,学生的编程体验也几乎没有差别。它们学习软件编程所积累的知识完全可以用于硬件部分。

而在创作类平台中(比如scratch),交互式编程允许虚拟角色与物理硬件彼此沟通,你可以自由联通虚拟与现实世界,制作体感游戏和富有表现力的故事。这将为我们带来更高的高天花板和更多的趣味性,从而点燃大家的热情

关于这块的有趣例子可以参考:scratch3-adapter-docs gallery

强大的可扩展性

交互式还将为我们带来强大的可扩展性,ROS(Robot Operating System)scratch3_adapter是很好的两个例子。因为基于消息通信,各个部分彼此解耦,这些系统本质上是分布式的,你可以接入任何东西,在scratch3_adapter中,硬件方面,我们已经接入了:

AI方面,我们接入了:

如果你愿意,你可以将小时候的玩具四驱车接入进来。

如何实现

everything is a message

天色已晚,明天要早起,这部分就不多写了,之后单独讨论

如果你熟悉LISP,为了实现一个 LISP REPL,只需要实现read、eval、print三个函数和一个不停轮询的函数(loop)即可,一个基本的REPL可以用如下的简单形式表达:(loop (print (eval (read))))

一旦你理解了这个概念,就可以自己动手去硬件上实现了。

事实上,社区里的交互式项目(如S4A、snap4arduino、s4m)思路基本都是一样的

我们自己动手实现了scratch3_adapter

我把思考和构建scratch3_adapter的过程都记录在博客里,如果你有兴趣,也可以根据我的架构设计,自己实现:

当然我更鼓励你加入我们,我们已经为开发者准备好了开发手册

我私下认为scratch3_adapter基于zeromq的实现也许是目前扩展性最好的,欢迎入坑 :)

补遗

通讯补丁

灌入式编程在一定程度上,也可以实现交互特性,一种策略是采用通讯变量的概念, makeblock的mblock很好地利用了这点

利用通讯变量,你可以让上下位机实现彼此沟通

技术实现的话,也不会太难,在硬件里跑一个独立的通信进程就行,之后通过uart或其他通道与上位机沟通就行

你最好将这种方式视为一种补丁,通讯变量是一种补充策略,通过这种补丁,你无法做到交互式的所有特性,但这依然不失为一种很聪明的做法

关于灌入式与交互式的混合

如何看待一个编辑器同时支持灌入式与交互式风格?

我的看法是:

do one thing and do it well

大体来说,我不认同这种做法。我不知道什么是好的方案来协调这两者,能兼得二者的好处,如果你有好的建议,愿意分享就太棒啦。

我目前只看到一些反模式(anti-pattern)。为了同时支持灌入式与交互式,意味着需要模式的切换,无论这种切换是不是智能的。都会导致积木无法彼此兼容(当然你可以做到局部兼容),用户如果希望组合两类积木来实现创意,在UI上他们的确拼在一起了,语义上也正确,这时编辑器弹出错误提示说: 灌入式积木与交互式积木无法组合使用,用户会非常沮丧,而且摸不着头脑。此外,有些积木两种模式都能用(诸如逻辑积木、数字积木),而有些积木则不行(有些需要灌入硬件,有些需要在网页emit消息),那么使用者还要时刻担心这种组合是否合适,提心吊胆倒也罢了,更糟的是错误出现的时候,他不知道是究竟是自己逻辑错了,还是混搭错了种类。这是对使用者的影响。

接下来说说它如何增加编辑器的复杂度。

以上问题当然有解决方案,诸如增强check功能、强化积木的类型,用做一个IDE的策略来做,上边列出的冲突总能够得到某种程度的缓和。但就这个编辑器本身而言,复杂度的提升,软件一致性的削弱,意味着出bug概率增多。

如果非要在一个编辑器中兼容这两种模式,有3种方案不算太坏:

其一是通讯补丁的策略,在这种模式下,用户的编程范式不需要变换,本质是交互式。灌入代码是为了获得交互的能力。

其二是显式地切换模式。诸如一些公司用electron打包scratch3.0,通过切换target来切换模式,同时禁用其他积木。

其三是单向切换:积木->代码。 这种方案在技术上可以做到完美无bug,但依然无法解决用户心理模型的问题。

值得注意的是,scratch3.0的插件系统架构并不基于target。scratch3.0的插件系统架构本质上适合交互式。这是官方团队权衡后的结果。

在我看来,这是一种强行整合,丝毫没有理念上的一致性,它致力于达到"我有这个功能"。

总而言之,我不赞同兼容这两者的方案。

关于这个话题的更多内容,参考我和@Cyke的这个讨论

参考