Mathematica:什么是符号编程?

84

我非常喜欢Stephen Wolfram,但他肯定不会吝啬夸自己。在许多参考文献中,他称Mathematica为一种不同的符号编程范例。我不是Mathematica的用户。

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

5个回答

77

当我听到“符号编程”这个词组时,我立刻想到了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会显示这张红色和绿色圆圈的图片:

alt text

但乐趣并不止于此。 在所有这些符号糖下面,我们仍然有一个符号表达式。 我可以应用另一种变换规则:

% /. Red -> Black

alt text

Voila! The red circle is now black.

Symbolic programming is known for this kind of "symbol pushing," which involves transforming expressions. A majority of Mathematica programming falls under this category.

Functional vs. Symbolic

Symbolic programming attempts to model everything with expression transformations, while functional programming models everything with functions. Both paradigms allow for easy abstraction building. For instance, the example above could be replicated in Haskell by utilizing a functional reactive animation approach. Functional programming relies on function composition, higher-level functions, combinators, and other functions-related features.

Mathematica heavily favors symbolic programming, although functional features can still be utilized. However, these features are simply a thin layer over transformations and sometimes abstract poorly (see footnote below).

Conversely, Haskell specializes in functional programming, even though symbolic programming is achievable. The differences lie in the syntactic representation of programs and data, making the symbolic programming experience inferior.

Closing Thoughts

I maintain that there is a clear difference between functional programming (e.g., Haskell) and symbolic programming (e.g., Mathematica). Studying both can provide a richer understanding than focusing solely on one paradigm.


Is the Functional Abstraction in Mathematica Leaky?

Yes, indeed. Here's an example:

f[x_] := g[Function[a, x]];
g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]];
f[999]

已向WRI报告并得到确认。回应是:避免使用 Function[var, body]Function[body] 没问题)。


3
WRI真的建议你避免使用Function[var, body]吗?这很奇怪,因为文档中推荐使用它... - Simon
8
@Simon: 是的,我收到了WRI的一封电子邮件,告诉我如果我担心一个函数的语义会根据"调用链"中任何调用者是否使用同名符号而改变,那么我应该避免使用Function[var, body]。没有提供关于为什么无法修复这个问题的解释,但我猜测由于Function从1.0版本就存在,所以在这个时候更改它的行为将是灾难性的。这个问题在这里稍微详细地描述了一下。 - WReach
5
在 MMA 中,由于其内部的曝光程度,我甚至不确定“Function”在原则上是否能够得到修复,至少当前的“Rule”和“RuleDelayed”的干扰语义不尊重内部作用域结构的绑定,包括它们自己。这种现象对我来说似乎更与“Rule”和“RuleDelayed”的这种属性有关,而非特别针对“Function”。但无论如何,我同意现在改变这一点非常危险。太糟糕了,因为不应该使用“Function[var,body]” - 这种错误在大型项目中几乎不可能被捕获。 - Leonid Shifrin
@M.Alaggan FYI,看起来Mathics已经迁移到这里,它没有超过第一个版本(1.0),但是它有一些相当新的提交(2018年11月)。 - jrh
@PierreALBARÈDE 是的,那确实是泄漏的根本原因,解决方法是使用槽符号表示法。现在我在处理Function以及其他类似情况时都只使用槽符号表示法,因为使用命名参数会很危险。 - WReach
显示剩余2条评论

75
你可以将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。如果你想让 add 只用于数值类型的 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[some stuff]”。

可以使用条件定义规则,这些条件本身需要通过规则重写以产生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版本的一个缺点是,当您需要使用Mathematica的默认求值器(以利用IntegrateSolve等)并且想要更改默认的求值顺序时,会出现问题。虽然这是可能的,但比较复杂,我认为未来某个符号编程的实现将有一种更优雅的控制求值顺序的方式。

2
@Yaro 当你将函数的结果作为规则(例如Solve、DSolve等)时,事情变得更加有趣。 - Dr. belisarius
但是你可以把 Solve 看作是另一组重写规则。当你给出一些 Mathematica 无法解决的方程时,Solve[hard_equations] 仍然保持为 Solve[hard_equations],你可以定义一个自定义的 Solve 规则来应用于这种情况。在这种情况下,我猜他们使用 /; 条件来定义“任何可以用 Mathematica 方法解决的方程”的模式,因此对于难解的方程,内置规则不适用,Solve 保持原始形式。 - Yaroslav Bulatov
2
我认为这样会使它变得太复杂了,Mathematica程序基本上只是一组替换规则。执行过程是将现有规则应用于输入的过程,直到没有规则匹配为止。 - Yaroslav Bulatov
7
+1 非常好的简明解释,没有吹嘘。也许我想补充的是,内核中已经包含了无数规则和算法,几乎涵盖了大多数语言中可用的数学库,还有更多其他内容。 - Dr. belisarius
3
Simon,lambda演算本身只是重写系统中的一个。术语重写是比任何特定TRS更一般的方法。 - SK-logic
显示剩余3条评论

11

正如其他人在这里提到的那样,Mathematica进行了很多术语重写。也许Haskell并不是最好的比较对象,但Pure是一种不错的函数术语重写语言(对于有Haskell背景的人应该会感到熟悉)。也许阅读他们关于术语重写的维基页面会为您解决一些问题:

http://code.google.com/p/pure-lang/wiki/Rewriting


1
从语义上讲,Pure 更像是 Scheme(动态类型,缺省为急切求值)或 OCaml(具有显式引用单元)。但其语法和库确实与 Haskell 相似。 - dubiousjim

6
Mathematica在使用术语重写方面非常重要。该语言为各种形式的重写提供特殊语法,规则和策略的特别支持。这种编程范式并不是“新”的,当然也不是独一无二的,但他们肯定处于“符号编程”领域的前沿,与其他强大的玩家(如Axiom)并驾齐驱。
至于与Haskell的比较,嗯,你可以在那里进行重写,再加上scrap your boilerplate库的一点帮助,但这绝对不像在动态类型的Mathematica中那样容易。

1

"Symbolic"与"Functional"不应该相互对比,而是应该与"数值编程"进行对比。以MatLab和Mathematica为例,假设我想要获得一个矩阵的特征多项式。如果我想在Mathematica中完成这个任务,我可以将单位矩阵(I)和矩阵(A)输入Mathematica,然后执行以下操作:

Det[A-lambda*I]

我会得到特征多项式(不要在意可能有特征多项式函数),另一方面,如果我在MatLab中,我不能用基本的MatLab来做这件事,因为基本的MatLab(不要在意可能有特征多项式函数)只擅长计算有限精度数字,而不是有随机lambda(我们的符号)的东西。你需要购买Symbolab附加组件,然后定义lambda作为自己的代码行,然后写出这个(其中它会将您的A矩阵转换为有理数矩阵而不是有限精度小数矩阵)。虽然在这种情况下性能差异可能不明显,但相对速度上来说,它可能比Mathematica慢得多。
所以这就是区别,符号语言感兴趣的是以完美的准确性进行计算(通常使用有理数而不是数值),而另一方面,数值编程语言非常擅长大多数你需要做的计算,并且它们 tend to be faster at the numerical operations they're meant for(MatLab在这方面几乎无与伦比,排除C++等高级语言),但在符号操作方面很差。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接