当我听到“符号编程”这个短语时,LISP、Prolog 和(是的)Mathematica 立即跃入脑海。我将符号编程环境描述为用于表示程序文本的表达式也恰好是主要数据结构的环境。因此,在抽象之上构建抽象变得非常容易,因为数据可以很容易地转换为代码,反之亦然。
Mathematica 大量利用了这种能力。甚至比 LISP 和 Prolog (恕我直言)。
作为符号编程的示例,请考虑以下事件序列。我有一个如下所示的 CSV 文件:
r,1,2
g,3,4
我在以下位置阅读了该文件:
Import["somefile.csv"]
--> {{r,1,2},{g,3,4}}
结果是数据还是代码?两者兼而有之。它是读取文件产生的数据,但它也恰好是构造该数据的表达式。然而,随着代码的运行,这个表达式是惰性的,因为评估它的结果就是它本身。
所以现在我对结果应用一个转换:
% /. {c_, x_, y_} :> {c, Disk[{x, y}]}
--> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}
不详述细节,所发生的只是Disk[{...}] 被包裹在每个输入行的最后两个数字上。结果仍然是数据/代码,但仍然是惰性的。另一个转变:
% /. {"r" -> Red, "g" -> Green}
--> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}
是的,仍然是惰性的。然而,非常巧合的是,最后一个结果恰好是 Mathematica 内置的特定领域图形语言中的有效指令列表。最后一次转变,事情开始发生:
% /. x_ :> Graphics[x]
--> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]
实际上,您不会看到最后的结果。在句法糖的史诗般的展示中,Mathematica 将展示这张红色和绿色圆圈的图片:
但乐趣不止于此。在所有这些语法糖的下面,我们仍然有一个象征性的表达。我可以应用另一个转换规则:
% /. Red -> Black
快!红色圆圈变成黑色。
正是这种“符号推送”是符号编程的特征。绝大多数 Mathematica 编程都属于这种性质。
函数式 vs. 符号式
我不会详细讨论符号编程和函数式编程之间的区别,但我会提供一些评论。
人们可以将符号编程视为对以下问题的回答:“如果我尝试仅使用表达式转换对所有事物进行建模会发生什么?”相比之下,函数式编程可以被视为以下问题的答案:“如果我尝试仅使用函数对所有事物进行建模会发生什么?”就像符号编程一样,函数式编程可以很容易地快速构建抽象层。我在这里给出的示例可以很容易地在 Haskell 中使用功能性反应动画方法重现。函数式编程是关于函数组合、更高级别的函数、组合器——所有你可以用函数做的漂亮事情。
Mathematica 明显针对符号编程进行了优化。可以以函数式风格编写代码,但 Mathematica 中的函数式特性实际上只是对转换的一层薄薄的外衣(并且是一种泄漏的抽象,请参见下面的脚注)。
Haskell 显然针对函数式编程进行了优化。可以以符号样式编写代码,但我会狡辩说,程序和数据的 句法 表示非常不同,从而导致体验欠佳。
结束语
最后,我主张函数式编程(以 Haskell 为代表)和符号编程(以 Mathematica 为代表)之间存在区别。我认为,如果一个人同时学习两者,那么一个人将比只学习一个学习更多——这是对独特性的终极测试。
Mathematica 中的函数抽象泄漏?
是的,漏水。试试这个,例如:
f[x_] := g[Function[a, x]];
g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]];
f[999]
向世界资源研究所正式报告并得到其承认。回复:避免使用Function[var, body](Function[body]可以)。