Mathematica: what is symbolic programming?

18,482

Solution 1

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

Solution 2

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).

Solution 3

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

Solution 4

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.

Solution 5

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.

Share:
18,482

Related videos on Youtube

zenna
Author by

zenna

Electronic Engineer, Biomedical Engineer, C++/CUDA

Updated on August 31, 2020

Comments

  • zenna
    zenna over 3 years

    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)?

  • Simon
    Simon about 13 years
    Did WRI really advice you to avoid Function[var, body]? That's strange since it's recommended in the docs...
  • WReach
    WReach about 13 years
    @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.
  • Leonid Shifrin
    Leonid Shifrin about 13 years
    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.
  • Mohammad Alaggan
    Mohammad Alaggan over 12 years
    @WReach Mathics, which is a FOSS Mathematica clone doesn't have the leaky Function problem! I just tried it.
  • dubiousjim
    dubiousjim almost 12 years
    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.
  • jrh
    jrh over 5 years
    @M.Alaggan FYI, it looks like Mathics has moved here, it didn't get past the first release (1.0) but it has some fairly recent commits (Nov of 2018)
  • Pierre ALBARÈDE
    Pierre ALBARÈDE over 2 years
    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.
  • WReach
    WReach over 2 years
    @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.

Related