Being functional, and having no "quotation", i.e. essentially treating data and programs semantically same, Mathematica seems naturally capable of doing metaprogramming. But could anyone construct a more thorough analysis about this? If you think there is any missing elements that prohibit Mathematica from doing (practical) metaprogramming, could you give an example?
-
9Meta-programming is a potentially broad topic, could you narrow it down with some examples? – rcollyer Feb 26 '12 at 02:41
3 Answers
What this answer is and is not
To avoid some confusion and misunderstanding, let me state right away what is the intended status of this answer.
This answer is not
- A tutorial to the subject
- A systematic, or complete, introduction to the subject
- An authoritative answer putting the final word on the subject
This answer hopefully is
- An (subjective!) overview of various meta-programming techniques in Mathematica, in the way they are known to me. I want to explicitly state that I am not trying to convey any kind of the "common wisdom" here, since the answer is largely based on my own experiences, and I have not seen an overwhelming number of meta-programming examples in Mathematica-related resources I had a chance to get acquainted with (so I may have no idea what the common wisdom is :)).
- A collection of (hopefully relevant) links with some minimal explanations, which would allow the reader to see some examples and applications of metaprogramming in Mathematica, or at least examples of what I consider meta-programming in Mathematica.
- A possible stub for some future answers, so that this larger one could be eventually rewritten and/or split into more focused and narrow ones, as the interest towards some particular forms of metaprogramming in Mathematica is being developed in our community.
Preamble
Ok, let me give it a shot. I'll start by claiming that Mathematica is very well suited for meta-programming, and one can write much more powerful programs in Mathematica by utilizing it. However, while it allows for very interesting and powerful meta-programming techniques, it does not IMO provide a convenient layer of tools to make these techniques more standard and effortless. Particularly painful is the evaluation control (preventing pieces of code from premature evaluation), because of the absence of the true quotation mechanism (here I will disagree with some other answers), the infinite evaluation model of Mathematica, and a quite complex core evaluator.
Enumerating some meta-programming techniques
There are several forms of meta-programming, so let me give a partial list first, and discuss afterwards
- Introspection-based metaprogramming
- Reflection-based metaprogramming (like in say, Java)
- Run-time code generation
- Macros (like in Lisp)
- DSL (domain-specific-language) creation
- ...?
In addition to these, Mathematica has its own meta-programming devices, such as rule-based metaprogramming and the Block-related techniques.
Introspection
Mathematica is IMO very strong here. There are a couple of reasons for this:
Homoiconic language (programs written in own data structures - Mathematica expressions. This is code-as-data paradigm, like Lisp which uses lists for this)
One can access global definitions for symbols stored in
OwnValues,DownValues,SubValues,UpVaulues, etc, and various other global properties, programmatically.Rule-based destructuring techniques (using
Casesetc) seriously simplify many introspection-related operationsMathematica code is "over-transparent" - even pure functions are expressions, available to introspection and destructuring, rather than black boxes. This has its downsides (for example, making a functional abstraction leaky in Mathematica, see the end of this answer), but it also allows for things like
withGlobalFunctionsmacro from this answer, where global function definitions are expanded inside pure functions (that macro also illustrates other meta-programming techniques).
Automatic dependency tracking
I will give a single simple explicit example of what I mean by introspection here, and supply some references to more involved cases. The following line of code gives all the symbols used to build a given expression expr, kept unevaluated:
Cases[Unevaluated[expr],s_Symbol:>HoldComplete[s],{0,Infinity},Heads->True]
Note that this will work for any Mathematica expression, including a piece of (perhaps unevaluated) Mathematica code.
A good illustration of introspection-based meta-programming is the symbol dependency analysis. I gave it a shot here, where I fully used all of the above-mentioned features (homoiconic language, low-level access to symbol's properties, rule-based destructuring). A simpler but practical application of dependency analysis can be found e.g. in the getDependencies function from this answer, where I do use the dependencies to dynamically construct a set of symbols which are encapsulated (not easily available on the top-level) but whose definitions must be saved during the serialization of the list object being constructed.
Working around some language limitations
Sometimes, introspection-based metaprogramming can be also used to go around certain limitations of the language, or to make the language constructs behave in the way you want while minimally affecting them. Some examples off the top of my head: changing the default behavior of SaveDefinitions option for Manipulate, making patterns to match only children of certain elements, and also two functions from this answer: a function casesShielded which implements a version of Cases that shields certain sub-expressions (matching specific pattern) from the pattern-matcher. and a (rather hacky) function myCases which implements a modified depth-first search, where the head is inspected before the elements (this is not what is happening in standard Cases, which sometimes has unwanted consequences). Yet another example here is the tiny framework I wrote to deal with the leaks of standard lexical scoping mechanism in Mathematica, which can be found here.
Summary
To conclude this section, I think that introspection-based meta-programming is a very useful and powerful technique in Mathematica, and the one that is relatively easy to implement without engaging in a fight with the system. I am also positive that it is possible to factor out the most useful introspection primitives and have a higher-level introspection-based metaprogramming library, and hope such a library will emerge soon.
Reflection - based metaprogramming
This may probably be considered a subset of the introspection-based metaprogramming, but it is particularly powerful for languages which impose more rigid rules on how code is written, particularly OO languages (Java for example). This uniform and rigid structure (e.g. all code is in classes, etc) allows for automatic querying of, for example, the methods called on the object, etc. Mathematica per se is not particularly powerful here, because "too many ways of doing things" are allowed for this to be effective, but one can surely write frameworks and / or DSLs in Mathematica which would benefit from this meta-programming style.
Run-time code generation
This type of meta-programming can be used relatively easily and brings a lot to the table in Mathematica.
Automation and adding convenient syntax
I will give a small example from this answer, where an ability to generate a pure function (closure) at run-time allows us to easily define a version of SQL select with a more friendly Mathematica syntax, and based on the in-memory Mathematica representation of an SQL table as a nested list:
ClearAll[select, where];
SetAttributes[where, HoldAll];
select[table : {colNames_List, rows__List}, where[condition_]] :=
With[{selF = Apply[Function, Hold[condition] /.
Dispatch[Thread[colNames -> Thread[Slot[Range[Length[colNames]]]]]]]},
Select[{rows}, selF @@ # &]];
Please see the aforementioned answer for examples of use. Further developments of these ideas (also based on meta-programming) can be found in this and this discussions.
Making JIT-compiled functions, and using Compile in more flexible ways
An important class of applications of run-time code-generation is in improving the flexibility of Compile. A simple example would be to create a JIT-compiled version of Select, which would compile Select with a custom predicate:
ClearAll[selectJIT];
selectJIT[pred_, listType_] :=
selectJIT[pred, Verbatim[listType]] =
Block[{lst},
With[{decl = {Prepend[listType, lst]}},
Compile @@
Hold[decl, Select[lst, pred], CompilationTarget -> "C",
RuntimeOptions -> "Speed"]]];
This function actually illustrates several techniques, but let me first show how it is used:
test = RandomInteger[{-25, 25}, {10^6, 2}];
selectJIT[#[[2]] > 0 &, {_Integer, 2}][test] // Short // AbsoluteTiming
selectJIT[#[[2]] > 0 &, {_Integer, 2}][test] // Short // AbsoluteTiming
(*
==> {0.4707032,{{-6,9},{-5,23},{-4,4},{13,3},{-5,7},{19,22},<<489909>>,{11,25},{-6,5},
{-24,1},{-25,18},{9,19},{13,24}}}
==> {0.1250000,{{-6,9},{-5,23},{-4,4},{13,3},{-5,7},{19,22},<<489909>>,{11,25},{-6,5},
{-24,1},{-25,18},{9,19},{13,24}}}
*)
The second time it was several times faster because the compiled function was memoized. But even including the compilation time, it beats the standard Select here:
Select[test,#[[2]]>0&]//Short//AbsoluteTiming
(*
==> {1.6269531,{{-6,9},{-5,23},{-4,4},{13,3},{-5,7},{19,22},<<489909>>,{11,25},{-6,5},
{-24,1},{-25,18},{9,19},{13,24}}}
*)
The other techniques illustrated here are the use of constructs like Compile@@Hold[...] to fool the variable-renaming scheme (see e.g. this answer for a detailed explanation), and the use of With and replacement rules (pattern-based definitions) as a code-injecting device (this technique is used very commonly). Another example of a very similar nature is here, and yet another, very elegant example is here.
Custom assignment operators and automatic generation of function's definitions
Another class of run-time code-generation techniques (which is somewhat closer to macros in spirit) is to use custom assignment operators, so that you can generate rather complex or large (possibly boilerplate) code from relatively simple specifications. Applications range from relatively simple cases of adding some convenience/ syntactic sugar, such as e.g. here (where we define a custom assignment operator to allow us to use option names directly in code), to somewhat more complex cases like making replacements in definitions at the definition-time, as say in the function lex from this answer (see also the code for a LetL macro below), to quite sophisticated generation of boilerplate code, happening e.g. in JLink behind the scenes (which, for JLink, is a big deal, because this (plus of course the great design of JLink and Java reflection) is the reason why JLink is so much easier to use than Mathlink).
Automating error-handling and generating boilerplate code
Yet another use for run-time code generation (similar to the previous) is to automate error-handling. I discussed one approach to that here, but it does not have to stop there - one can go much further in factoring out (and auto-generating) the boilerplate code from the essential code.
A digression: one general problem with various meta-programming techniques in Mathematica
The problem with this and previous classes of use cases however is the lack of composition: you can not generally define several custom assignment operators and be sure that they will always work correctly in combinations. To do this, one has to write a framework, which would handle composition. While this is possible to do, the development effort can rarely be justified for simple projects. Having a general library for this would be great, provided that this is at all possible. In fact, I will argue that the lack of composibility ("out of the box") is plaguing many potentially great meta-programming techniques in Mathematica, particularly macros.
Note that I don't consider this being a fundamental core language-level problem, since the relevant libraries / frameworks can surely be written. I view it more as a consequence of the extreme generality of Mathematica and it being in a transition from a niche scientific language to a general-purpose one (in terms of its typical uses, not just capabilities), so I am sure this problem has a solution and will eventually be solved.
Proper (macro-like) run-time generation of Mathematica code
A final use case for the run-time code generation I want to mention is, well, run-time Mathematica code generation. This is also similar to macros (as they are understood in Lisp) in spirit, in fact probably the closest to them from all techniques I am describing here. One relatively simple example I discuss here, and a similar approach is described here. A more complex case involving generation of entire packages I used for the real-time cell-based code highlighter described here. There are also more sophisticated techniques of run-time Mathematica code generation - one of which (in a very oversimplified form) I described here
Summary
To summarize this section, I view run-time code generation as another meta-programming technique which is absolutely central to make non-trivial things with Mathematica.
Macros
First, what I mean by macros is probably not what is commonly understood by macros in other languages. Specifically, by macro in Mathematica I will mean a construct which:
- Manipulates pieces of Mathematica code as data, possibly preventing them from (premature) evaluation
- Expands code at run-time (not "read-time" or "compile-time", which are not so well defined in Mathematica)
Some simple examples
Here is the simplest macro I know of, which allows one to avoid introducing an intermediate variable in cases when something must be done after the result has been obtained:
SetAttributes[withCodeAfter,HoldRest];
withCodeAfter[before_,after_]:=(after;before)
The point here is that the argument before is computed before being passed in the body of withCodeAfter, therefore evaluating to the result we want, while the code after is being passed unevaluated (due to the HoldRest attribute), and so is evaluated already inside the body of withCodeAfter. Nevertheless, the returned result is the value of before, since it stands at the end.
Even though the above macro is very simple, it illustrates the power of macros, since this kind of code manipulation requires special support from the language and is not present in many languages.
Tools used for writing macros
The main tools used for writing macros are tools of evaluation control, such as
Hold*- attributes,EvaluateandUnevaluated- code injection using
Withand / or replacement rules - Pure functions with
Hold- attributes
Even in the simple example above, 2 of these tools were used (Hold-attribute and replacement rules, the latter hidden a bit by using global replacement rules / definitions). The discussion of the evaluation control constructs proper is outside the scope of the present discussion but a few places you can look at are here and here
Typical classes of macros
Macros can widely range in their purpose. Here are some typical classes
- Making new scoping constructs or environments (very typical use case)
- Used in combination with run-time code generation to inject some unevaluated code
- Used in combination with some dynamic scoping, to execute code in some environments where certain global rules are modified. In this case, the "macro" - part is used to delay the evaluation until the code finds itself in a new environment, so strictly speaking these are rather custom dynamic scoping constructs.
Examples of new scoping constructs / environments
There are plenty of examples of the first type of macros available in the posts on StackOverlflow and here. One of my favorite macros, which I will reproduce here, is the LetL macro which allows consecutive bindings for With scoping construct:
ClearAll[LetL];
SetAttributes[LetL, HoldAll];
LetL /: Verbatim[SetDelayed][lhs_, rhs : HoldPattern[LetL[{__}, _]]] :=
Block[{With}, Attributes[With] = {HoldAll};
lhs := Evaluate[rhs]];
LetL[{}, expr_] := expr;
LetL[{head_}, expr_] := With[{head}, expr];
LetL[{head_, tail__}, expr_] :=
Block[{With}, Attributes[With] = {HoldAll};
With[{head}, Evaluate[LetL[{tail}, expr]]]];
What it does is to expand a single declaration like LetL[{a=1,b=a+1,c = a+b},a+b+c] into a nested With at run-time, and it also works for function definitions. I described in more fully here (where some subtleties associated with it are also described), and used it extensively e.g. here. A very similar example can be found in this answer. Yet another example I already mentioned - it is the macro withGlobalFunctions from this answer, which expands all generically-defined (via patterns) global functions. The last example I want to include here (although it also is relevant for the third use case) is a macro for performing a code cleanup, discussed here, and I particularly like the version by @WReach, which I will reproduce here:
SetAttributes[CleanUp, HoldAll]
CleanUp[expr_, cleanup_] :=
Module[{exprFn, result, abort = False, rethrow = True, seq},
exprFn[] := expr;
result =
CheckAbort[
Catch[Catch[result = exprFn[]; rethrow = False; result], _,
seq[##] &], abort = True];
cleanup;
If[abort, Abort[]];
If[rethrow, Throw[result /. seq -> Sequence]];
result]
It is not fully "bullet-proof", but does a really good job in the majority of cases.
Examples of run-time code generation / new functionality
Actually, many of the above examples also qualify here. I'll add just one more here (in two variations): the abortable table from this answer (I will reproduce the final version here):
ClearAll[abortableTableAlt];
SetAttributes[abortableTableAlt, HoldAll];
abortableTableAlt[expr_, iter : {_Symbol, __} ..] :=
Module[{indices, indexedRes, sowTag, depth = Length[Hold[iter]] - 1},
Hold[iter] /. {sym_Symbol, __} :> sym /. Hold[syms__] :> (indices := {syms});
indexedRes = Replace[#, {x_} :> x] &@ Last@Reap[
CheckAbort[Do[Sow[{expr, indices}, sowTag], iter], Null],sowTag];
AbortProtect[
SplitBy[indexedRes, Array[Function[x, #[[2, x]] &], {depth}]][[##,1]] & @@
Table[All, {depth + 1}]
]];
(it accepts the same syntax as Table, including the multidimensional case, but returns the partial list of accumulated results in the case of Abort[] - see examples of use in the mentioned answer), and its version for a conditional Table, which only adds an element is certain condition is fulfilled - it is described here. There are of course many other examples in this category.
Examples of dynamic environments
Dynamic environments can be very useful when you want to modify certain global variables or, which is much less trivial, functions, for a particular piece of code, so that the rest of the system remains unaffected. The typical constructs used to achieve this are Block and Internal`InheritedBlock.
The simplest and most familiar dynamic environment is obtained by changing the values of $RecursionLimit and / or $IterationLimit inside a Block. Some examples of use for these are in my answer in the discussion of tail call optimization in Mathematica. For a more complex example, see my suggestion for the recent question on convenient string manipulation. Some more examples can be found in my answer to this question. An example of application of this to profiling can be found here.
Again, there are many more examples, many of which I probably missed here.
Problems with writing macros in Mathematica
To my mind, the main problems with writing and using macros consistently in Mathematica are these:
- Hard to control evaluation. No real quotation mechanism (
HoldandHoldCompletedon't count because they create extra wrappers, andUnevaluateddoes not count since it is not permanent ans is stripped during the evaluation) - Macros as described above are expanded from outside to inside. Coupled with the lack of real quotation mechanism, this leads to the absence of true macro composition out of the box. This composition can be achieved, but with some efforts
- The lack of the real compilation stage (The definition-time does not fully count since most definitions are delayed).
To circumvent these issues, one has to apply various techniques, such as
- Trott - Strzebonski in-place evaluation technique to evaluate parts of held expressions in-place (see also this answer for some more details on that)
- A technique which I call (for the lack of a better name) "inverse rule-dressing", which exploits the properties of delayed rule substitution (delayed, plus intrusive), to inject some unevaluated code. I used it in the first solution in this answer, in more complex way in the
SavePointersfunction in this answer, and in a number of other cases. It has also been used very elegantly in this answer. - using a custom
Hold-like wrapper which is first mapped on (possibly all) parts of an expression, and later removed using rules. Two examples of this techniques are here and here - ...
Despite all these techniques being useful, and in total covering most of the needs for macro-writing, the need to use them (often in combinations) and the resulting code complexity shows, to my mind, the serious need for a generic library which would provide simpler means for macro-writing. I would prefer to be able to nest macros and not think about zillion of things that may go wrong because of some unwanted evaluation, but rather about things that really matter (such as variable captures).
Summary
Macros are another very powerful meta-programming technique. While it is possible to write them in Mathematica, it is, as of now, a rather involved undertaking, and composing macros is an even harder task. Because composition in the key, I attribute the fact that macros are not in widespread use in Mathematica programming, to this lack of composition, plus the complexity of writing individual macros. That said, I think this is a very promising direction, and hope that some time soon we will have the tools which would make writing macros a more simple and automatic process.
DSL creation
I won't say almost anything here, except noting that this is entirely possible in Mathematica, and some nice syntax can be added easily via UpValues.
Final remarks
I think that meta-programming is one of the most important and promising directions in the present and future of Mathematica programming. It is also rather complex, and IMO, largely unexplored in Mathematica still. I hope that this justifies this post being so long.
I tried to summarize various approaches to meta-programming in Mathematica, which I am aware of, and give references to examples of these approaches, so that the reader can look for him/herself. Since meta-programming is a complex topic, I did not attempt to write a tutorial, but rather tried to summarize various experiences of myself and others to produce a kind of a reference.
One may notice that the references are dominated by the code I wrote. One reason for that is that I am a heavy user of meta-programming in Mathematica. Another reason is that everyone remembers own code the most. I have to apologize for not including some other references which did not come to my mind right away. I invite everyone to edit this post and add more references, which I missed.
- 114,335
- 15
- 329
- 420
-
12LOL -- I just got an automatic moderator flag telling me: "Post is excessively long." Troublemaker. :-P – Mr.Wizard Feb 26 '12 at 17:03
-
-
8
-
9@Mr.Wizard, rcollyer Sorry guys, I really would have loved to make this shorter, but giving justice to this topic is really tough - I probably did not succeed even half-way. And ignoring this would be IMO a very wrong thing to do, for me personally and the community as a whole - because, I think, meta-programming is the future of Matematica programming. – Leonid Shifrin Feb 26 '12 at 17:20
-
I'm finally done. This has to be your magnum opus on StackExchange. As a matter of professional interest, how long did it take you to compose this answer? – Mr.Wizard Feb 26 '12 at 17:28
-
@Mr.Wizard About 3 hours, I think. I was first planning for a much shorter answer, but, as often happens to me, the answer started to live its own life and dictated the rest. I wouldn't have done that for pretty much any other topic, I think - but this one I consider extremely important (apart from the fact that this is also the topic of the most interest to me). – Leonid Shifrin Feb 26 '12 at 17:31
-
@Mr.Wizard I actually don't think this one is much longer that the one on file-backed lists, which took me two days to do, since for that I had to do some real development. – Leonid Shifrin Feb 26 '12 at 17:33
-
9I'm reorganising my year schedule, so that by the end of the year I'll be done studying this post – Rojo Feb 26 '12 at 18:55
-
1@Rojo Wow, I did not expect such a strong reaction. I am not sure that this answer is kept to standards high enough to meet such a serious response. Actually, I will try to improve this answer in the future, so I think it should be trated as no more than a zero'th order approxmation here. – Leonid Shifrin Feb 26 '12 at 19:11
-
3Hehe, ok, I exaggerated, I admit it. But you know, I don't have much formal computer science training. What I know it's stuff I've read around. I handled C++ but that knowledge was rotting because I lacked what it took to have useful and fun projects with it. Mathematica for me has been a door. Some time ago I got to the point of feeling powerful enough with it so that it was convenient to use it for so many things. That's when I knew that in time I wouldn't forget it, cause it just became practical to use it for the stuff I needed to do. Work, study, taking notes, teach, investigate... – Rojo Feb 26 '12 at 19:26
-
That's when it became a door into the rest of the computer world. Now I didn't need to learn a huge lot in order for my C++ knowledge to be in the same state of usefullness... I just needed to plug it into Mathematica. Same fate with SQL, now CUDA, .NET, soon Java, etc. – Rojo Feb 26 '12 at 19:28
-
@Rojo It was a door for me too (I had some prior experiences with Basic, Pascal, Fortran and Matlab, but on a pretty basic level) . I picked up C, Java and Javascript afterwards, but as far as programming is concerned, Mathematica remains my first love and my strongest suit, at least as of now :). And I also have the same perception and long-time goal to use it for cross-language development (actually, my highlighter was one step towards that). – Leonid Shifrin Feb 26 '12 at 19:32
-
My point is, these posts help me not so much on the "Mathematica tricks" area, but in learning stuff about computer science, getting some structure, while allowing me to give use to it right away, by plugging them into MMA. Of course I could read some books and stuff, but this is more natural, requires no effort, hehe – Rojo Feb 26 '12 at 19:32
-
@LeonidShifrin, please make this shorter - it is impossible to comment on this epic! Hold (and friends), are a mechanism to generalize quotation in M-. The fact that hold is an attribute come from hold not being implemented as a special form but as a general mechanism for any function. Also, quotation creates an additional wrapper, that is simply striped of in eval. – Feb 26 '12 at 20:39
-
@ruebenko I will eagerly make this shorter once there appear more targeted and narrow questions on this topic. In this answer, wanted to mostly summarize the links to examples I am aware of, related to various forms of metaprogramming, this is not intended to be the final answer. I could leave out all explicit code examples, but that would make the aswer very dry. I could use very simplistic examples but that would leave out important things. I could narrow things down but that would be a point answer. I think, that as broad as it is, this answer can be of use as an overview. – Leonid Shifrin Feb 26 '12 at 21:03
-
@ruebenko I also think (or, hope) that the answer as it is reflects the state of meta-programming in Mathematica (or at least, known techniques). For languages with more standardized meta-programming techniques and tools, the answer could have been more organized as well (or may be it is just me, but I was very much into mma metaprogramming in the last few years, and would be surprised to be totally off). Regarding commenting: I use sections / subsections to not put it all together, so you should be able to comment on specific points I make. You can also edit my answer if you consider ... – Leonid Shifrin Feb 26 '12 at 21:10
-
@ruebenko ...something being totally off, wrong or misrepresented. I will try to rearrange the answer some time soon, but it is not as easy as it may seem, unless we want to sacrifice either breadth, or links / examples. Regarding
Hold- attributes: what I said reflects my personal experiences in writing macros, and I do that a lot. I don't question the fact thatHold-attributes are general - to the opposite, I claim that convenient macro-writing does not need (and even suffers from) this generality. I want to have my code (treated as data) totally inert during the macro-expansion. – Leonid Shifrin Feb 26 '12 at 21:17 -
Don't apologize for doing a subject justice. I was being sarcastic in calling you a trouble maker, and it should be taken as a complement. I view your larger answers just like I view a text like Jackson, it is layered like an onion, and every read you peal off a layer. But, there are always more layers. – rcollyer Feb 27 '12 at 02:22
-
@rcollyer Thanks - I am just rather dissatisfied with this answer myself, on several grounds. I will try to see if I could rework it, when I get more time. – Leonid Shifrin Feb 27 '12 at 16:09
-
5I have to disagree with some of the comments suggesting that this answer is too long or diffuse--rather, it strikes me as a concise and appropriate response to a very broad question, and kudos to you, Leonid, for even attempting it. I always find that your pragmatic discussions of sometimes subtle concepts in the light of formidable practical experience (and an apparently photographic memory for examples) provide ample food for thought. – Oleksandr R. Feb 27 '12 at 18:17
-
1@Oleksandr Thanks a lot, I really appreciate. This was bothering me all day today, since from all my answers this was the one where I was questioning my judgement the most. This was in part because this was largely based on my personal experiences, while all my particiaption in MathGroup, SO and here did not provide me with the strong sense of common wisdom on this particular topic. I used to do Physics before, where a bunch of self-citations in an article is usually a bad sign :). I am particularly happy to get this supporting comment from you. – Leonid Shifrin Feb 27 '12 at 18:39
-
I think there's a general lack of experience with, or perhaps even recognition of, these sorts of techniques within the Mathematica community. Aside from you and Roman Maeder, few people seem to have taken metaprogramming seriously enough to use in real applications. Personally, I find these methods very interesting, but I am not quite confident enough with the implementation (robustness, etc.) to use them broadly. From my point of view, your perspective on these topics is not only extremely valuable, but almost unique. – Oleksandr R. Feb 27 '12 at 18:58
-
1@Oleksandr Thanks! I am realy very interested in meta-programming techniques possible in Mathematica. I am trying new stuff every now ad then, some of it works, some doesn't. I also have a feeling that these techniques are underused in Mathematica community, which is ironic because meta-programming is by definition about automation of your own work, something that scientists should strive for more than many other professions. I firmly believe that a lot of these techniques are still waiting for us undiscovered. Often, I am annoyed that the language is both powerful and resistant at the ... – Leonid Shifrin Feb 27 '12 at 20:53
-
2@Oleksandr ...same time. I also came to a conclusion (long ago in fact), that I can make further progress in Mathematica only by leaving it and learning other powerful languages (Lisp or Ml family, perhaps), to learn powerful programming techniques used there. I did it once already with C, which made me a much better Mathematica programmer. But Mathematica has a strong gravity field, hard to get far away from:). And Roman Maeder is one of the people I admire and look up at. His books remain for me what I'd call "The Zen of Mathematica programming". He is still light years ahead :). – Leonid Shifrin Feb 27 '12 at 20:58
-
Now with the comments after mine and the 100 rep bounty, whose strong reaction you didn't expect Leonid, huh? Hehe – Rojo Mar 06 '12 at 14:26
-
@Rojo I meant to say that for this particular question, my answer was largely based on my own experiences, so I had no idea how interesting or relevant that might be for the OP and generally the community, and neither was I ready for the responsibility that I must accept if this answer becomes canonical in any sense :) – Leonid Shifrin Mar 06 '12 at 14:31
-
You don't choose the wand, the wand chooses you... Err, that made more sense for @Mr.Wizard – Rojo Mar 06 '12 at 14:38
-
Leonid, it is the very fact that this answer is largely based on your own experience that makes it valuable as original work. You've been posting these methods for some time but this answer finally brings it all together in a coherent fashion. – Mr.Wizard Mar 06 '12 at 14:41
-
@Mr.Wizard Thanks. I actually wanted to bring these things together for quite a while already (for my own good). This was an opportunity to do it, where I could justify the time because it could possibly be useful for others as well. – Leonid Shifrin Mar 06 '12 at 15:23
-
It appears leaving the bounty active rather than immediately awarding it is having the desired effect. Five new votes so far, I believe, and another Good Answer badge. :-) – Mr.Wizard Mar 06 '12 at 23:09
-
@Mr.Wizard Oh, now I see. I was wondering why all these extra votes, and did not conect those to the bounty (although I saw it). I owe you now :) – Leonid Shifrin Mar 07 '12 at 05:54
-
-
-
An interesting function to add to this discussion is [
Experimental`CompileEvaluate](http://reference.wolfram.com/mathematica/Experimental/ref/CompileEvaluate.html) which first compiles and then evaluates an expression. It performs favorably against your JIT example, above, roughly halving the initial timing. It does not accept anyOptionsthough, so tuning would require "globally" setting them. – rcollyer Apr 04 '12 at 14:27 -
@rcollyer Very interesting, I did not know. Will have a look when I get time, and it might be a good idea to mention it in my answer as you suggested, but this seems to be on topic even more for
Compile-related posts, because here I was illustrating what you can do with meta-programming. It would be interesting to see if one can use meta-programming to make a general function like that, and if so, how would it compare toCompileEvaluate. In any case, this is surely very much worth looking at, thanks. – Leonid Shifrin Apr 04 '12 at 14:35 -
-
@Artes Thanks a bunch, man! Should start getting used to a new guru status :) – Leonid Shifrin Jul 10 '12 at 21:14
-
1Totally agree that metaprogramming is the future of Wolfram / Mathematica. I am really feeling the lack of a comprehensive, composable methodology for it as I attempt a translation of a large body of common lisp code to MMA. For now, I am backing off metaprogramming per se because the effort is turning into bikeshedding on my real task (translating the lisp code). I have just read "Let Over Lambda," "Land of Lisp," "On Lisp," and "Practical Common Lisp" and now opine that creating in Wolfram the level of MP available in CL and Clojure will be a multi-year job. – Reb.Cabin Oct 19 '14 at 01:13
-
1@Reb.Cabin In fact, I wanted to do the same, in the sense of reading Lisp books / learning that stuff, and then trying to get some of that into Mathematica. But I agree, that's a lot of work. Still, I'd really like to do that, at least in part, when I get more time on my hands. For instance, I've written some preliminary code for custom code loader in Mathematica, which would have a real read time, making real read-time macros possible. But serious work in any of those directions requires much more time than I currently have. – Leonid Shifrin Oct 19 '14 at 11:14
-
-
@ciao Glad to hear that! By the way, may I ask why you decided to leave this community (if that is so)? Your contributions and participation here are very valuable. It would be a great pity if you leave. – Leonid Shifrin Apr 23 '15 at 22:10
-
@ciao And thanks for your intent regarding a very generous bounty! But as I said, I would personally much appreciate you staying in the community (if possible of course), and many others surely would also. – Leonid Shifrin Apr 23 '15 at 22:38
-
@ciao Thanks, I've just received your bounty :). But I feel uneasy about this, still. You know, I am ready to return back several times as many points, if that would help keep you on the site. We've all been through the frustration regarding the inadequacy of rep. My own take is that since some point, I started to simply ignore rep. The fact is, this site has a ton of quality answers, and people like us move it forward to make even better. I believe that we have what matters the most: strong, nice and active community, and tons of really useful stuff exposed already or waiting to be exposed. – Leonid Shifrin Apr 24 '15 at 22:31
-
@LeonidShifrin: I'll still be reading, and trying to better existing answers on interesting questions, just privately. This issue for me is like putting an expensive price-tag on cheap wine: readers seeing big upvotes on RTFM/ et. al. answers that should have been insta-closed leaves a bad taste in my mouth. In any case, it is quite satisfying to rectify this as best I can by spreading the wealth. Almost as fun as when I do it with real money... – ciao Apr 24 '15 at 22:36
-
Leonid, this is of course a great piece you have written, and is still great 5 years later. What would the missing 'real quoting mechanism' that you mention for MMA look like? That is, what sort of syntax would you imagine it have? 5 years later, have there been any interesting developments in MMA/WL that you would add to this discussion? Are we any closer to getting macros in MMA? – berniethejet Nov 25 '20 at 18:59
-
1@berniethejet That's an interesting question. Regarding quotation mechanism, the closest thing I have seen that can be done reasonably easily on the top-level, is described in this answer . What I meant however was a separate read step in the loader, which would allow read-time macros. Unfortunately, my uses of meta-programming in recent years have been mostly more pedestrian. So I don't have much truly new to add in this department. – Leonid Shifrin Nov 25 '20 at 19:24
-
1@berniethejet I also later realized that the full solution would have to include a few interrelated components, such as a new module system (which would de-emphasize contexts), a new package manager, a new code loader, and then read-time macros as a part of that. But such a project would've been an ambitions undertaking, and besides, it is not clear to me whether it has any chance to succeed if not backed by the WRI in the centralized manner. And partial solutions will probably be ridden by inconsistencies of various kind. – Leonid Shifrin Nov 25 '20 at 19:29
-
1@berniethejet But to tell you the truth, I simply didn't have the time in all these years to sit down and really think these things through in enough detail. Some bits of understanding were coming every now and then, and now I probably have a much better mental picture of all this than when this post was written. If I happen to have more time on my hands (not likely to happen very soon, but you never know), it would be an exciting thing to try my hands at. – Leonid Shifrin Nov 25 '20 at 19:32
-
@Leonid Shifrin Of course you know much better, but I also agree that it is always a big gamble to do any sorts of packages without some sort of WRI backing. (Does 'the centralized manner' mean 'Stephen'? ;-) ) So what does 'separate read step in the loader' mean? It sounds like some sort of interception of the standard evaluation sequence that would require explicit internal hooks in the kernel. Is this something that could be done without such support? – berniethejet Nov 26 '20 at 17:58
-
1@berniethejet What I meant was a stage when the code has been loaded and parsed, but no evaluation has been performed. At that point one basically should have the parsed code at their disposal, and read-time macros would expand then. The full expanded code will then be evaluated. This can surely done without any additional support, one just needs to write one's own code loader, that would replace the standard one. This is possible to do on the top-level if we somewhat constrain the problem / limit the functionality, I've actually done some experiments on that a few years ago. – Leonid Shifrin Nov 26 '20 at 19:05
-
1@berniethejet But the full solution which would replace and completely incorporate / supersede the current code loader (
Get) would be quite hard to code without the support from the kernel. It would have to correctly treat MX files, support all the peculiarities of existing loader to be backward-compatible, be fast, etc. – Leonid Shifrin Nov 26 '20 at 19:09 -
@ Leonid 'Get is the current code loader'!?! I suppose it is, I had never thought of it as such - at least not as the generalized code loading mechanism. Does that mean that one would do 'Get[yaddayadda, Method -> "String"]' for this? Does this then mean that ' " ' (i.e. standard string quotes) are MMA's 'real quoting mechanism'? It is interesting that the "String" Method for Get isn't even mentioned until the MMA 12.1 documentation! – berniethejet Dec 03 '20 at 15:29
-
1@berniethejet I actually didn't mean string code import. I meant parsing but not evaluating, something similar to
Import[file, "HeldExpressions"]- except the latter is rather slow. – Leonid Shifrin Dec 03 '20 at 17:16 -
Now there exists an internal lisp macro-inspired metaprogramming package in GeneralUtilities: Macro Package – StackExchanger Dec 27 '22 at 01:13
-
@StackExchanger Well, it has been there for ages. But thanks for the info. I knew about it, but never looked into it closely enough. Perhaps, I should. – Leonid Shifrin Dec 27 '22 at 22:33
I'm not to present any thorough analysis if metaprogramming is viable, but I could give a hint towards this issue. I don't think there is any fundamental barrier for metaprogramming in Mathematica, but I doubt we could develop this way anything really powerful.
To point out a practical obstacle (though by any means not a no-go theorem) I suggest to take a closer look at The Mathematica One-liner Competition. There is a nice example of entertaining metaprogramming in Mathematica by Yves Papegay titled "Don't Evaluate This" with an annotation "...if only I had a better machine!".
Select[ Flatten@ Map[ FromCharacterCode@Tuples[ Range[32, 129], #] &,
Range@80 ], SyntaxQ]
As the Wolfram blog says this expression selects from all
200696776371546515671027031705365217492618488158283260021075576209690090503635023
3077746752088222272458708782885444148423180502637853488332240652972952399993950
possible expressions of 80 characters or less those that are syntactically valid Mathematica inputs. Besides it is practically useless, the expression itself is a member of the set that it describes.
I hope it points out one important practical problem of metaprogramming in general though shows Mathematica capabilites making it possible.
For example we generate a kind of meaningful output :
Select[ Flatten@Map[ FromCharacterCode@Tuples[ Range[42,62], #] &, Range[3, 4]],
SyntaxQ][[46536 ;; 46546]]
{"97+5", "97+6", "97+7", "97+8", "97+9", "97--", "97-0", "97-1", "97-2", "97-3", "97-4"}
- 57,212
- 12
- 157
- 245
-
The range of printable ascii code should be Range[32, 126], not Range[32, 129]. – btwiuse Jan 15 '17 at 16:53