Mathematica: 什么是符号编程?

我是斯蒂芬 · 沃尔夫拉姆的超级粉丝,但是他绝对是一个敢于自吹自擂的人。在许多参考文献中,他赞扬 Mathematica 是一个不同的象征性编程范型。我不是 Mathematica 的用户。

我的问题是: 这个符号编程是什么? 它与函数式语言(比如 Haskell)相比如何?

18916 次浏览

Mathematica 正在大量使用术语重写。该语言为各种形式的重写提供了特殊的语法,对规则和策略提供了特殊的支持。这种模式并不是那么“新”,当然也不是独一无二的,但是他们绝对处于这种“符号编程”的前沿,与其他强大的参与者一起,如 Axiom。

至于与 Haskell 的比较,你可以在那里重写代码,只需要废弃样板库的一点帮助,但是它并不像动态类型的 Mathematica 那样简单。

您可以将 Mathematica 的符号编程看作是一个搜索和替换系统,通过指定搜索和替换规则进行编程。

例如,您可以指定以下规则

area := Pi*radius^2;

下次使用 area时,它将被替换为 Pi*radius^2。现在,假设您定义了新规则

radius:=5

现在,无论何时使用 radius,它都会被重写为 5。如果你计算 area,它会被重写到 Pi*radius^2,这会触发 radius的重写规则,你会得到 Pi*5^2作为一个中间结果。这个新表单将触发 ^操作的内置重写规则,因此表达式将被进一步重写到 Pi*25中。此时,由于没有适用的规则,重写将停止。

您可以使用替换规则作为函数来模拟函数式编程。例如,如果您想定义一个添加的函数,您可以这样做

add[a_,b_]:=a+b

现在 add[x,y]被重写为 x+y。如果你想要添加只适用于数字 a,b,你可以这样做

add[a_?NumericQ, b_?NumericQ] := a + b

现在,使用您的规则将 add[2,3]重写到 2+3,然后使用 +的内置规则重写到 5,而 add[test1,test2]保持不变。

下面是一个交互式替换规则的示例

a := ChoiceDialog["Pick one", {1, 2, 3, 4}]
a+1

在这里,a被替换为 ChoiceDialog,然后被替换为用户在弹出的对话框中选择的数字,这使得 +的数量为数字并触发替换规则。在这里,ChoiceDialog作为一个内置的替换规则,类似于“用用户点击的按钮的值替换 ChoiceDialog [一些东西]”。

规则可以使用条件来定义,这些条件本身需要通过规则重写来生成 TrueFalse。例如,假设你发明了一种新的方程求解方法,但是你认为它只有在你的方法的最终结果是正面的时候才会起作用。您可以执行以下规则

 solve[x + 5 == b_] := (result = b - 5; result /; result > 0)

在这里,solve[x+5==20]被替换为15,但是 solve[x + 5 == -20]没有变化,因为没有适用的规则。阻止此规则应用的条件是 /;result>0。求值器本质上是查看规则应用程序的潜在输出,以决定是否继续执行它。

Mathematica 的计算器贪婪地用适用于该符号的规则之一重写每个模式。有时候,您希望有更好的控制,在这种情况下,您可以定义自己的规则,并像这样手动应用它们

myrules={area->Pi radius^2,radius->5}
area//.myrules

这将应用在 myrules中定义的规则,直到结果停止更改。这与默认计算器非常相似,但是现在您可以有几组规则并有选择地应用它们。更高级的 例子演示了如何创建一个类似 Prolog 的计算器,用于搜索规则应用程序序列。

当您需要使用 Mathematica 的默认计算器(以使用 IntegrateSolve等)时,会出现当前 Mathematica 版本的一个缺点,还有希望更改默认计算序列。这是可能的,但是 很复杂,我喜欢认为,一些符号编程的未来实现将有一个更优雅的方式来控制求值顺序

当我听到短语“符号编程”,LISP,Prolog 和(是的) Mathematica 立即跳到脑海中。我将描述一个符号编程环境,在这个环境中,用于表示程序文本的表达式恰好也是主要的数据结构。因此,很容易在抽象上构建抽象,因为数据可以很容易地转换为代码,反之亦然。

Mathematica 大量利用了这种能力,甚至比 LISP 和 Prolog (IMHO)还要多。

作为符号编程的一个示例,请考虑以下事件序列。我有一个 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 展示了这张红色和绿色圆圈的图片,这是一个关于语法糖的史诗般的展示:

alt text

但乐趣不止于此。在所有这些语法糖下面,我们仍然有一个象征性的表达。我可以应用另一个转换规则:

% /. Red -> Black

alt text

转眼间,红色的圆圈变成了黑色。

符号编程的特点就是这种“符号推送”。绝大多数 Mathematica 节目都是这种性质的。

功能性与符号性

我不会详细讨论符号编程和函数编程之间的区别,但是我会提供一些意见。

人们可以把符号编程看作是对这个问题的回答: “如果我试图仅使用表达式转换对所有内容建模,会发生什么情况?”相比之下,函数式编程可以被看作是对以下问题的回答: “如果我试图仅使用函数对所有内容建模,会发生什么情况?”与符号编程一样,函数式编程使得快速构建抽象层变得容易。我在这里给出的示例可以很容易地在 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]

向 WRI 适当报告并得到 WRI 的认可。对此的回应是: 避免使用 Function[var, body](Function[body]是可以的)。

正如这里的其他人已经提到的,Mathematica 进行了大量的术语重写。也许 Haskell 不是最好的比较,但是 纯的是一个很好的功能性术语——重写语言(对于有 Haskell 背景的人来说应该感觉很熟悉)。也许阅读他们关于术语重写的维基页面会让你明白一些事情:

Http://code.google.com/p/pure-lang/wiki/rewriting

符号不应该与函数形成对比,而应该与数值编程形成对比。以 MatLab 和 Mathematica 为例。假设我想要矩阵的特征多项式。如果我想在 Mathematica 这样做,我可以得到一个身份矩阵(i)和矩阵(a)本身到 Mathematica,然后这样做:

Det[A-lambda*I]

另一方面,如果我在 MatLab 中,我就不能用 base MatLab 来计算特征多项式(不用担心可能有一个特征多项式函数) ,因为 base MatLab (不用担心可能有一个特征多项式函数)只擅长计算有限精度的数字,而不是那些有随机 lambdas (我们的符号)的东西。你需要做的就是购买附加的 Symbolab,然后定义 lambda 作为它自己的代码行,然后写出来(在这里它会把你的 A 矩阵转换成一个有理数矩阵,而不是有限精度的小数) ,虽然性能差异可能不会引起这样的小情况下,它可能会比 Mathematica 的相对速度慢得多。

所以这就是不同之处,符号语言对完美精确的计算感兴趣(通常使用有理数而不是数字) ,而数字编程语言在另一方面非常擅长你需要做的绝大多数计算,而且它们往往在它们应该做的数字运算方面更快(在这方面,MatLab 几乎是无与伦比的高级语言——不包括 C + + 等) ,而且在符号运算方面很糟糕。