陈凯
维特根斯坦有一个著名的且引发了广泛讨论的论断:“世界是所有事实的总和,而不是所有事物的总和。”这句话可能离普通人的日常经验有一些距离,当人们看待世界的时候,往往自然而然地倾向于把事物进行分解:从某个物体还原到分子,再还原到原子,进而到各种基本粒子,于是可能觉得这整个世界就是由这些基本粒子组成的。一些哲学家觉得这样看待世界的方式是不全面的,世界上的事物之间是有着普遍联系的,当谈论事物的时候,实际上总是无法割裂事物之间的关系来谈论事物本身。这里不考虑哲学家们各不相同的世界观,若撇开现实世界的情况,将目光集中在所谓的“元宇宙”上,那么,“元宇宙”中的世界的特征的确与维特根斯坦所说的“事实的总和”有相当程度的契合。想象一下,某人在“元宇宙”中喝咖啡,这个过程中咖啡的实体实际是不存在的,咖啡的味道可能是一系列信息对你头脑神经元的冲击,也就是说,只有喝咖啡这件事存在而已。
“元宇宙”是一个在极短时间里就火爆起来的词语,它其实指的是一种与人脑高度融合的虚拟世界(而不只是把脑袋罩在一个产生立体影像和声音的头盔里),这让人联想起电影《黑客帝国》《头号玩家》,或动画片《刀剑神域》之类。当前,“元宇宙”尚未进入到日常生活中,但不妨以“元宇宙”的视角来看待许多事实,如程序语言的语法为何是当前所看到的样子?
什么是“元宇宙”的视角?“元(meta)”这个字本身有着自我指涉的意味,如元数学是用数学的技术来研究数学本身,元数据是用数据来描述数据,元规则是思考如何制定规则去制定某个规则。然而,“元宇宙”不能简单理解为“用宇宙的方法来描述宇宙”,“元宇宙”的“元”更接近于“元编程”的“元”,对照在编程过程中对编程行為本身的理解和改变的“元编程”,那么“元宇宙”其实就是在虚拟空间的体验中理解和改变虚拟空间自身。“元”的概念提示了信息技术学科教学中一种独特的“真实情境”,那就是以我们所使用的各种信息技术工具(尤其是编程工具)本身为情境,自反性地去思考它们何以如此,并且将会如此。
● 积木式编程环境里的事物和关系
当目光凝视在编程本身,而不是用编程的语句解决某个现实问题的时候,就会由这种凝视产生出具有自反性特征的思考。本文主要围绕积木式编程环境中的积木组装与日常所用的以文字、符号组成的编程语句这两者间的差异来展开思考。
积木式编程环境中提供了各种各样形状的零件,如果仅仅是将它们分散地拖拽到编辑空间中,那么它们只是看上去存在着,却没有任何意义,除非这些积木能被合理地相互拼接起来,这样,每一块积木似乎就有了特定的价值。这让人想起了“意义即存在”的哲学议题,至少在积木式编程环境中,那些未被合理组装到一起而是散落在编辑空间中的积木块,就直接被忽略掉了。一段合理的、表达程序代码的组装积木图形是一系列表达事物和表达关系的积木拼接嵌合而成,没有事物的关系与没有关系的事物,都是无法用来正确描述出某个功能的。
尽管存在很多种积木式编程平台,但它们的使用方法是大同小异的。以下活动应当在学生们对积木式编程环境有初步了解和应用后开展,希望学生们不要过多纠结于积木组装后的实际用途,而以纯粹拼图的方式来审视每一块积木自身的形状,审视积木之间的拼接关系,并在一些方面开展实验并作总结:
哪些拼图可以用在语句起始?
哪些拼图可以和上面或下面的语句连接?
哪些拼图可以嵌入到其他拼图中去?哪些总是必须嵌入到其他拼图中?
哪些拼图既可以嵌入到其他拼图中,也可以在其中嵌入其他拼图?
哪些拼图的纵向宽度会发生变化?为什么这种变化是必须的?
……
积木拼接的形式是需要考虑的重点。与借由键盘输入文字、符号的形式来编程的方式对比,积木式编程实现了对语句的输入行为的较为严格限定,这样就大大提高了编程初学者完成任务的成功率。如图1所示,如果说一段普通的用文字、符号的形式写就的程序代码表达了某一系列的计算机的计算行为,那么积木式编程环境中组装成功的积木就是对某段用文字、符号的形式写就的程序代码的表达,也就是一种表达的表达。正是这种二阶的表达对表达的方式进行了限定。
在拼接积木的过程中,有两个方向不同的问题值得更深入地思考:其一,是否编程者想要描述的合理的功能,都能够被这些积木拼出来?其二,任意采用积木拼接出来的图形,都能对应某个合理的功能吗?类似这样的问题,就是元编程问题。
● 形式结构的生成和检验
从积木式编程的体验中带来的一个启发是,可以通过限定性的规则来约束语句的结构。这个思想可以迁移到其他编程环境中,对于借由键盘输入文字、符号的形式来编程的大部分环境,可以鼓励学生们自行创设限定性规则来实现某种拼接游戏,并检验其可行性,图2是其中的一个例子。
以下是拼接规则:所有非字母符号和数字的符号不能当作最左面第一块积木;等于号后可以接括号;当相邻左右括号内部无物时,可以在其中放置其他积木,但必须满足要么两侧都是同类型括号相连,要么两侧都是括号与井号相连的形式;拼接完成后删除井号和句号,并试着运行代码。
如果只通过复制和粘贴来产生语句(除了数字需要通过键盘输入),则上述实验其实模拟了积木式编程产生赋值语句的方法。例如,先放置:
a=
接下来可以放置带括号的积木:
a=()
接下来可以往括号内放置数字:
a=(#12#)
到这里,就不再有继续拼接的可能了。将井号删除后,得到赋值语句a=(12)。
在a=()这一步后也可以放置其他积木,如:
a=(()*())
只要相邻左右括号是空的,就可以继续按规则放置,如:
a=((#3#)*(()+()))
……
通过这样的方式能得到规范的赋值语句。这样,就能体验到形式在结构生成中的作用。
换一种角度,假设积木式编程里的积木并没有设定这些规则,而是允许随意拼接,那怎样才能知道,拼接后的哪一些图案是合理的?如果说,先前的问题是怎么限定规则,使得拼接过程必然生成合法的语句,而现在的问题是,怎么设定规则,使得在任意生成的语句的集合中,检验哪些语句符合规则而留存,哪些不符合规则而被过滤掉。
这个问题涉及形式文法的检验,撇开相关专业知识,可利用网络上现成的工具来做直观的实验。网站http://web.stanford.edu/class/archive/cs/cs103/cs103.1156/tools/cfg/上提供有一个形式文法的检验机,如图3所示。这里举两个简单的例子,如有下面的生成规则:
S →SD | a | b | c | d
D→| a | b | c | d | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
这两条规则说明这一类变量名需要以a或b或c或d开头,后面可以跟上a或b或c或d,也可以跟上某个数字符号,長度不限。很容易利用这个工具检验某个符号串是否符合以上的文法规则。规则中,“ε”符号表示空,“|”符号表示或,大写字母表示可被其他大写字母、小写字母或数字替代。
而如图4所示的这些规则,就能生成合法的赋值语句,为了避免过于复杂,这里没有考虑括号的使用。
可以仅像对待一个智力玩具那样对待这个形式文法检验工具,将任意的符号组合而成的字符串都交由这个工具进行检验,如果字符串被接受,那就说明这个字符串是通过预设的规则合法地产生出来的。
● 规则与自相似
审视积木式编程环境中的积木组装方式,可以清晰地看出复杂的结构是如何借助重复和嵌套的模式生成的。运算表达式里嵌套的运算表达式、双分支结构里的双分支结构、循环结构里的循环结构等,都具有自相似的特点,如图5所示。
在Python中,分支或循环结构中语句块的缩进,首要的目的当然是为了能够正确解析代码。在教学中,可以强调这种缩进来产生一种具有自相似结构的图形化美感。如下页图6所示,这段拼接而成的代码,展现了分支结构语句具有创设自相似结构迷宫的可能性。
如下页图7、图8所示,下面这段代码的作用,是通过输入布尔值来实现将二进制编码转换为十进制数字的功能,显然,仅有的这几行代码只能转换一位二进制数字,通过自相似的拼接,它就能扩展自己的功能,处理更多位二进制的数字。拼接的规则也很简单,就是将“#begin”和“#end”中的所有内容,复制粘贴到只有“#”的区域。这个自相似的拼接游戏可以反复进行下去。
在拼接过程中,每一个基本的语句块都是简单的,拼接过程也是简单的。但最终拼接而成的代码,却有着能让旁观者仿佛陷入迷宫的繁杂结构,这其实也是对现实世界运作规律的一种隐喻。最具有浓厚的自相似特征的程序语言大概就是LISP之类的表处理语言,所有的表达式都是以括号括起的函数名和数据的形式呈现的,图9就是一个用与非逻辑来实现异或逻辑的LISP代码。
这种风格的代码似乎并不是为人类阅读准备的,但正是这种仿佛强迫症一样的自相似结构,为一个按照程序代码运行的计算机器能够在运行代码过程中改写自身的代码提供了方便。LISP的出现曾经是人工智能研究里程中的一座高峰,它展现了依靠极少的运算符和极其简单的结构来实现复杂运算的能力,与自然世界的复杂事物的构成方式具有某种程度的相似性。但运行LISP程序的计算机器,仍然与人的思维能力相去甚远。在这里,回顾本文开始时提出的问题,如果说“元宇宙”是在虚拟空间的体验中理解和改变虚拟空间自身,那么LISP等表处理语言的能力已经为实现这样的理想提供了某种保证。但若要问“元宇宙”能否具有在虚拟的宇宙中理解和改变现实中的宇宙的能力,这个问题的答案却还十分模糊。
本文从积木式编程的特征谈论到普通程序语言中的自相似结构,再谈论到具有严格自相似特点的LISP语言,从历史的时间轴看,这是从现今向过往的回溯,从认识的深度看,这是从现象到本质的还原。