84

I am a big fan of Stephen Wolfram, but he is definitely one not shy of tooting his own horn. In many references, he extols Mathematica as a different symbolic programming paradigm. I am not a Mathematica user.

My questions are: what is this symbolic programming? And how does it compare to functional languages (such as Haskell)?

nbro
  • 15,395
  • 32
  • 113
  • 196
zenna
  • 9,006
  • 12
  • 73
  • 101

5 Answers5

77

When I hear the phrase "symbolic programming", LISP, Prolog and (yes) Mathematica immediately leap to mind. I would characterize a symbolic programming environment as one in which the expressions used to represent program text also happen to be the primary data structure. As a result, it becomes very easy to build abstractions upon abstractions since data can easily be transformed into code and vice versa.

Mathematica exploits this capability heavily. Even more heavily than LISP and Prolog (IMHO).

As an example of symbolic programming, consider the following sequence of events. I have a CSV file that looks like this:

r,1,2
g,3,4

I read that file in:

Import["somefile.csv"]
--> {{r,1,2},{g,3,4}}

Is the result data or code? It is both. It is the data that results from reading the file, but it also happens to be the expression that will construct that data. As code goes, however, this expression is inert since the result of evaluating it is simply itself.

So now I apply a transformation to the result:

% /. {c_, x_, y_} :> {c, Disk[{x, y}]}
--> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}

Without dwelling on the details, all that has happened is that Disk[{...}] has been wrapped around the last two numbers from each input line. The result is still data/code, but still inert. Another transformation:

% /. {"r" -> Red, "g" -> Green}
--> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}

Yes, still inert. However, by a remarkable coincidence this last result just happens to be a list of valid directives in Mathematica's built-in domain-specific language for graphics. One last transformation, and things start to happen:

% /. x_ :> Graphics[x]
--> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]

Actually, you would not see that last result. In an epic display of syntactic sugar, Mathematica would show this picture of red and green circles:

alt text

But the fun doesn't stop there. Underneath all that syntactic sugar we still have a symbolic expression. I can apply another transformation rule:

% /. Red -> Black

alt text

Presto! The red circle became black.

It is this kind of "symbol pushing" that characterizes symbolic programming. A great majority of Mathematica programming is of this nature.

Functional vs. Symbolic

I won't address the differences between symbolic and functional programming in detail, but I will contribute a few remarks.

One could view symbolic programming as an answer to the question: "What would happen if I tried to model everything using only expression transformations?" Functional programming, by contrast, can been seen as an answer to: "What would happen if I tried to model everything using only functions?" Just like symbolic programming, functional programming makes it easy to quickly build up layers of abstractions. The example I gave here could be easily be reproduced in, say, Haskell using a functional reactive animation approach. Functional programming is all about function composition, higher level functions, combinators -- all the nifty things that you can do with functions.

Mathematica is clearly optimized for symbolic programming. It is possible to write code in functional style, but the functional features in Mathematica are really just a thin veneer over transformations (and a leaky abstraction at that, see the footnote below).

Haskell is clearly optimized for functional programming. It is possible to write code in symbolic style, but I would quibble that the syntactic representation of programs and data are quite distinct, making the experience suboptimal.

Concluding Remarks

In conclusion, I advocate that there is a distinction between functional programming (as epitomized by Haskell) and symbolic programming (as epitomized by Mathematica). I think that if one studies both, then one will learn substantially more than studying just one -- the ultimate test of distinctness.


Leaky Functional Abstraction in Mathematica?

Yup, leaky. Try this, for example:

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

Duly reported to, and acknowledged by, WRI. The response: avoid the use of Function[var, body] (Function[body] is okay).

WReach
  • 18,098
  • 3
  • 49
  • 93
  • 3
    Did WRI really advice you to avoid `Function[var, body]`? That's strange since it's recommended in the docs... – Simon Mar 01 '11 at 21:29
  • 8
    @Simon: Yes, I have an email from WRI that states that if I am worried about the semantics of a function changing based upon whether any caller in the "call-chain" happens to use a like-named symbol, then I should avoid using `Function[var, body]`. No explanation was offered about why this could not be fixed, but I speculate that since `Function` has been around since 1.0 it would be disastrous to change its behaviour this late in the game. The problem is described in (slightly) more detail [here](http://weaklyreachable.blogspot.com/2007/06/mathematica-pure-function-scope-problem_2182.html). – WReach Mar 02 '11 at 00:40
  • 5
    With the level of exposure of its internals in mma, I am not even sure that `Function` could be cured, even in principle - at least with the current intruding semantics of `Rule` and `RuleDelayed`, which do not respect inner scoping constructs' bindings, *including themselves*. This phenomena seems to me more related to this property of `Rule` and `RuleDelayed`, than specifically to `Function`. But either way, I agree that changing this is very dangerous now. Too bad, because `Function[var,body]` should not be used - such bugs will be almost impossible to catch in sizable projects. – Leonid Shifrin May 03 '11 at 19:38
  • @WReach [Mathics](http://www.mathics.net/), which is a FOSS Mathematica clone doesn't have the leaky Function problem! I just tried it. – Mohammad Alaggan Nov 19 '11 at 01:59
  • @M.Alaggan FYI, it looks like Mathics has moved [here](http://mathics.github.io/), it didn't get past the first release (1.0) but it has some fairly recent commits (Nov of 2018) – jrh Jan 04 '19 at 12:52
  • You forgot to mention the output of `f[999]` is `Function[0, 999][0]`. This case is not minimal, there is a lot on top of it that blurs the essential. It reduces to `0 /. a_ :> Function[a, 999][a]` or `Unevaluated[Function[a,999][a]]/.a->0` which is normal because replacement occurs before `Function` evaluates. Although `Function[x,f@x]` is better than no `Function` at all, `Function[f@#]`is even better because it eliminates a possibility of name conflict. Thus in my opinion the advice of WR is this case is good and this case is not a bug, although the value may be surprising. – Pierre ALBARÈDE Dec 28 '21 at 21:49
  • @PierreALBARÈDE Yes, that is indeed the ultimate cause of the leak and the workaround is to use slot notation. I now use slot notation exclusively with `Function` for this and other similar cases that render the use of named parameters dangerous. – WReach Dec 30 '21 at 20:10
75

You can think of Mathematica's symbolic programming as a search-and-replace system where you program by specifying search-and-replace rules.

For instance you could specify the following rule

area := Pi*radius^2;

Next time you use area, it'll be replaced with Pi*radius^2. Now, suppose you define new rule

radius:=5

Now, whenever you use radius, it'll get rewritten into 5. If you evaluate area it'll get rewritten into Pi*radius^2 which triggers rewriting rule for radius and you'll get Pi*5^2 as an intermediate result. This new form will trigger a built-in rewriting rule for ^ operation so the expression will get further rewritten into Pi*25. At this point rewriting stops because there are no applicable rules.

You can emulate functional programming by using your replacement rules as function. For instance, if you want to define a function that adds, you could do

add[a_,b_]:=a+b

Now add[x,y] gets rewritten into x+y. If you want add to only apply for numeric a,b, you could instead do

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

Now, add[2,3] gets rewritten into 2+3 using your rule and then into 5 using built-in rule for +, whereas add[test1,test2] remains unchanged.

Here's an example of an interactive replacement rule

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

Here, a gets replaced with ChoiceDialog, which then gets replaced with the number the user chose on the dialog that popped up, which makes both quantities numeric and triggers replacement rule for +. Here, ChoiceDialog as a built-in replacement rule along the lines of "replace ChoiceDialog[some stuff] with the value of button the user clicked".

Rules can be defined using conditions which themselves need to go through rule-rewriting in order to produce True or False. For instance suppose you invented a new equation solving method, but you think it only works when the final result of your method is positive. You could do the following rule

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

Here, solve[x+5==20] gets replaced with 15, but solve[x + 5 == -20] is unchanged because there's no rule that applies. The condition that prevents this rule from applying is /;result>0. Evaluator essentially looks the potential output of rule application to decide whether to go ahead with it.

Mathematica's evaluator greedily rewrites every pattern with one of the rules that apply for that symbol. Sometimes you want to have finer control, and in such case you could define your own rules and apply them manually like this

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

This will apply rules defined in myrules until result stops changing. This is pretty similar to the default evaluator, but now you could have several sets of rules and apply them selectively. A more advanced example shows how to make a Prolog-like evaluator that searches over sequences of rule applications.

One drawback of current Mathematica version comes up when you need to use Mathematica's default evaluator (to make use of Integrate, Solve, etc) and want to change default sequence of evaluation. That is possible but complicated, and I like to think that some future implementation of symbolic programming will have a more elegant way of controlling evaluation sequence

Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197
  • 2
    @Yaro I guess the thing gets more interesting when you get rules as the result of functions (as in Solve, DSolve, etc) – Dr. belisarius Dec 13 '10 at 22:16
  • But you can think of `Solve` is just another set of rewriting rules. When you give some equations that Mathematica can't solve `Solve[hard_equations]` remains as `Solve[hard_equations]` and you can define a custom `Solve` rule that applies in this case. In this case, I'm guessing they use /; conditional to define pattern for "any equation that can be solved with methods in Mathematica", so for hard equations built-in rule doesn't apply and `Solve` remains in original form – Yaroslav Bulatov Dec 13 '10 at 22:36
  • So.. is Mathematica like a lambda-calculus language that's very late-binding (dynamic), and Haskell is one that tries to be as early-binding (static) as possible ? – Simon Michael Dec 14 '10 at 00:33
  • 2
    I think that's making it too complicated, Mathematica programs are basically just sets of replacement rules. Execution is a process of applying existing rules to input until no rule matches – Yaroslav Bulatov Dec 14 '10 at 01:29
  • 7
    +1 Very nice unconvoluted no-horn-blowing explanation. Perhaps the only thing I'd like to add is that there are gazillions of rules and algorithms already included in the kernel representing almost all the mathematical libraries available in most languages, and then some more. – Dr. belisarius Dec 14 '10 at 03:41
  • 3
    Simon, lambda calculus itself is just one of the rewrite systems. Term rewriting is a more general approach than any particular TRS. – SK-logic Dec 14 '10 at 10:20
  • Can anyone provide a reference to learn more about symbolic paradigms and quite possibly, how to even create a toy symbolic language? – Jeel Shah Aug 22 '16 at 15:42
  • re term re-writing, cf. REFAL from the 1960s. – Will Ness Aug 31 '18 at 07:28
11

As others here already mentioned, Mathematica does a lot of term rewriting. Maybe Haskell isn't the best comparison though, but Pure is a nice functional term-rewriting language (that should feel familiar to people with a Haskell background). Maybe reading their Wiki page on term rewriting will clear up a few things for you:

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

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • 1
    Semantically, Pure is more like Scheme (dynamic typing, defaults to eager evaluation) or OCaml (has explicit reference cells). But the syntax and libraries do mirror Haskell's. – dubiousjim Jun 22 '12 at 14:47
6

Mathematica is using term rewriting heavily. The language provides special syntax for various forms of rewriting, special support for rules and strategies. The paradigm is not that "new" and of course it's not unique, but they're definitely on a bleeding edge of this "symbolic programming" thing, alongside with the other strong players such as Axiom.

As for comparison to Haskell, well, you could do rewriting there, with a bit of help from scrap your boilerplate library, but it's not nearly as easy as in a dynamically typed Mathematica.

SK-logic
  • 9,605
  • 1
  • 23
  • 35
1

Symbolic shouldn't be contrasted with functional, it should be contrasted with numerical programming. Consider as an example MatLab vs Mathematica. Suppose I want the characteristic polynomial of a matrix. If I wanted to do that in Mathematica, I could do get an identity matrix (I) and the matrix (A) itself into Mathematica, then do this:

Det[A-lambda*I]

And I would get the characteristic polynomial (never mind that there's probably a characteristic polynomial function), on the other hand, if I was in MatLab I couldn't do it with base MatLab because base MatLab (never mind that there's probably a characteristic polynomial function) is only good at calculating finite-precision numbers, not things where there are random lambdas (our symbol) in there. What you'd have to do is buy the add-on Symbolab, and then define lambda as its own line of code and then write this out (wherein it would convert your A matrix to a matrix of rational numbers rather than finite precision decimals), and while the performance difference would probably be unnoticeable for a small case like this, it would probably do it much slower than Mathematica in terms of relative speed.

So that's the difference, symbolic languages are interested in doing calculations with perfect accuracy (often using rational numbers as opposed to numerical) and numerical programming languages on the other hand are very good at the vast majority of calculations you would need to do and they tend to be faster at the numerical operations they're meant for (MatLab is nearly unmatched in this regard for higher level languages - excluding C++, etc) and a piss poor at symbolic operations.

William Bell
  • 167
  • 2
  • 9