Mathematica's own programming model: functions and expressions
There are many books about Mathematica programming, still one sees many people falling to understand Mathematica's programming model and usually misunderstand it as functional programming.
This is, because one can pass a function as an argument, like
plotZeroPi[f_] := Plot[f[x], {x,0,Pi}];
plotZeroPi[Sin] (* produces Plot[Sin[x],{x,0,Pi}] *)
and so people tend to think that Mathematica follows a functional programming (FP) model. There is even a section in the documentation about functional Programming. Yes, looks similar, but it is different - and you will see shortly why.
Expressions are what evaluation is about
Everything in Mathematica is an expression. An expression can be an atom, like numbers, symbol variables and other built-in atoms, or a compound expression. Compound expressions -our topic here- have a head followed by arguments between square brackets, like Sin[x]
.
Thus, evaluation in Mathematica is the ongoing transformation from one expression to another based on certain rules, user-defined and built-in, until no rules are applicable. That last expression is returned as the answer.
Mathematica derives its power from this simple concept, plus a lot of syntactic sugar you have to write expressions in a more concise way… and something more we will see below. We don't intend to explain all the details here, as there are other sections in this guide to help you.
In fact, what happened above is the definition of a new head, plotZeroPi
via the infix operator :=
. More over, the first argument is a pattern expression plotZeroPi[f_]
, with head (as pattern) plotZeroPi
and a pattern argument. The notation f_
simply introduces an any pattern and gives it a name, f
, which we use in the right hand side as the head of another expression.
That's why a common way to express what f
is, is that plotZeroPi
has a function argument - although is not very precise-, and we also say that plotZeroPi
is a function (or a high-level function in FP lingo), although is now clear that there is a little abuse of the terminology here.
Bottom line: Mathematica looks like functional programming because one is able to define and pass around heads.
Putting evaluation on hold
But, note that Plot
does not expect a function, it expects a expression! So, although in a functional programming paradigm, one would write Plot
with a function parameter, in Mathematica plot expects an expression. This was a design choice in Mathematica and one that I would argue makes it quite readable.
This works because Plot
is flagged to hold the evaluation of its arguments (see non-standard). Once Plot
sets its environment internally, it triggers the evaluation of the expression with specific values assigned to x
. When you read the documentation, beware of this subtlety: it says function although a better term would have been expression.
Dynamically creating a head
So, what happens if one needs to perform a complex operation and once that is done, a function is clearly defined? Say you want to compute Sin[
$\alpha$ x]
, where $\alpha$ is the result of a complex operation. A naive approach is
func[p_, x_] := Sin[costlyfunction[p] x]
If you then try
Plot[func[1.,x], {x,0,Pi}]
you can be waiting long to get that plot. Even this does not work
func[p_][x_] := Sin[costlyfunction[p] x]
because the whole expression is unevaluated when entering Plot
anyway. In fact, if you try func[1.] in the front-end, you will see that Mathematica does not know a rule about it and can't do much either.
What you need is something that allows you to return a head of an expression. That thing will have costlyfunction
calculated once before Plot
takes your head (the expression's, not yours) and gives it an x
.
Mathematica has a built-in, Function
that gives you that.
func[p_] := With[{a = costlyfunction[p]}, Function[x, Sin[a x]] ];
With
introduces a new context where that costly function is evaluated and assigned to a
. That value is remembered by Function
as it appears as a local symbol in its definition. Function
is nothing but a head that you can use when needed. For those familiar with functional programming in other languages, a
is part of the closure where the Function
is defined; and Function
is the way one enters a lambda construct into Mathematica.
Another way to do it, more imperative if you like, is using Module
and what you already know about defining rules -which is more familiar to procedural programming-:
func[p_] := Module[{f, a},
a = costlyfunction[p];
f[x_] := Sin[a x];
f
];
In it, a new context is introduced with two symbols, f
and a
; and what it does is simple: it calculates a
, then defines f
as a head as we want it, and finally returns that symbol f
as answer, a newly created head you can use in the caller.
In this definition, when you try say, func[1.], you will see a funny symbol like f$3600
being returned. This is the symbol that has the rule f[x_] := Sin[a x]
attached to it. It was created by Module
to isolate any potential use of f
from the outside world. It works, but certainly is not as idiomatic as function
.
The approach with Function
is more direct, and there is syntactic sugar for it too; you will see it in regular Mathematica programming
func[p_] := With[{a = costlyfunction[p]}, Sin[a #]& ];
Ok, let's continue.
Now that func
really returns a function, i.e. something that you can use as the head of an expression. You would use it with Plot
like
With[{f = func[1.]}, Plot[f[x],{x,0,Pi}]]
and we bet that by this time you will understand why Plot[func[1.][x],{x,0,Pi}]
is as bad as any of the previous examples.
On returning an expression
A final example is Piecewise
(from the documentation)
Plot[Piecewise[{{x^2, x < 0}, {x, x > 0}}], {x, -2, 2}]
So, what if the boundary on the condition is a parameter? Well, just apply the recipe above:
paramPieces[p_] := Piecewise[{{#^2, # < p}, {#, # > p}}] &;
One shouldn't do
paramPieces[p_] := Piecewise[{{x^2, x < p}, {x, x > p}}];
because Piecewise
does not have the hold attribute and it will try to evaluate its argument. It does not expect an expression! If x
is not defined, you may see a nice output when you use it, but now you are constrained to use the atom (variable name) x
and although
Plot[paramPieces[0], {x, -1, 1}]
seems to work, you are setting yourself for trouble. So, how to return something you can use in Plot
?
Well, in this case, the parameter is not a burden to the calculation itself, so one sees this kind of definitions being used
paramPieces[p_, x_] := Piecewise[{{x^2, x < p}, {x, x > p}}];
Plot[paramPieces[0, x], {x,-1,1}]
And, if x is undefined, paramPieces[0, x]
is nicely displayed in the front-end as before. This works because, again, Mathematica is a expressions language, and the parameter x
makes as much sense as the number 1.23
in the definition of paramPieces
. As said, Mathematica just stops the evaluation of paramPieces[0, x]
when no more rules are applied.
A remark on assignment
We have said above several times that x
gets assigned a value inside Plot
and so on. Again, beware this is not the same as variable assignment in functional programming and certainly there is (again) abuse of language for the sake of clarity.
What one has in Mathematica is a new rule that allows the evaluation loop to replace all occurrences of x
by a value. As an appetizer, the following works
Plot3D[Sin[x[1] + x[2]], {x[1], -Pi, Pi}, {x[2], -Pi, Pi}]
There is no variable x[1]
, just a expression that gets a new rule(s) inside Plot
every time it gets a value for plotting. You can read more about this in this guide too.
Note to readers: Although these guides are not meant to be comprehensive, please, feel free to leave comments to help improve them.
f[x_]:=a; f[x_Integer]=b
; 2. Forgotten underscore in patternsf[x]=a
3.Set
vsSetDelayed
; 4.m = {{1, 2}, {3, 4}} // MatrixForm
and thenEigenvalues[q]
; 5. Plotting complex function produces empty plot without any warnings. $\endgroup$$HistoryLength
in there, a memory management in general category includingMaxMemoryUsed
andMemoryConstrained
etc $\endgroup$CurrentImage
). $\endgroup$