I would like to know an efficient method (without converting Mathematica Code to strings and string processing) of modifying a function definition programmatically. For example, I would like to tidy up a function's code by removing all Print statements that were introduced for debugging/tracing. What would be the best strategy here? I know there are questions on SE that deal with automatic Mathematica Code generation, but I want something simple, without elaborate parsing rules etc., accomplished perhaps by an application of replacement rules. I have tried to do this myself, but evaluation control presents a big obstacle. Any suggestions would be greatly appreciated.
3 Answers
This is my first time wading into metaprogramming in Mathematica, so take this with a pinch of salt. I can get the DownValues of myfunction and strip out the cases of Print commands, then build a new function newfunction by setting DownValues like so:
(* any old function will do *)
myfunction[x_, y_] := Module[{p = 0},
p = x^2 + y^2;
Print["test1: " <> ToString@p];
If[p < 1, p = p^2 + 1, p = y - x];
Print["test2: " <> ToString@p];
Do[
Print["blah" <> ToString@i];
, {i, 3}];
Return[p]
]
DownValues[newfunction] = ReleaseHold[
DeleteCases[
DownValues[myfunction][[1]],
fn_[___] /; fn === Print, Infinity,
Heads -> True
] /. myfunction -> newfunction
];
myfunction[6, 3]
(*
> test1: 45
> test2: -3
> blah1
> blah2
> blah3
returns -3
*)
newfunction[6,3]
(* returns -3 *)
You might want to look into ways to suppress Print though, because the above technique looks pretty dangerous and will probably go wrong in unexpected ways.
Suppress Print[ ]s?
- 25,147
- 2
- 20
- 86
-
The "refactoring" part is in
DeleteCases[DownValues[myfunction][[1]], fn_[___] /; fn === Print, Infinity, Heads -> True]if you want to copy that definition out. – flinty Aug 07 '20 at 15:02 -
Thank you flinty for something along the lines of what I tried myself but failed to succeed. You may be right, in that this might lead to unexpected results in the case of more complex expressions and function definitions. – Iconoclast Aug 07 '20 at 15:03
-
I am aware of "Suppress Pirint[]s?" question, but I would like to have more than that, i.e., code refactoring as you mentioned. – Iconoclast Aug 07 '20 at 15:05
To operate on notebooks
Let nb be the notebook you want to alter obtained with NotebookGet[]. For instance, it could be nb = NotebookGet[EvaluationNotebook[]]. Instead of EvaluationNotebook[], you could have something like First@Select[Notebooks[], Information[#, "FileName"] === "MyProg" &].
nb /. HoldPattern@RowBox[{
x___, Optional[";", ";"],
RowBox[{"Print", "[", ___, "]"}],
Optional[";", ";"], y___}] :>
RowBox[{x, y}] // NotebookPut
Note: This will not extensively tested. Like the method below, it may result in errors in the code. It should work well if each Print[] statement occurs in a CompoundExpression.
To operate on definitions in the kernel
The function cleanup[sym, pat] will delete all expressions matching pat from the definitions of a symbol sym. Use _Print to delete Print statements.
cleanup[sym_Symbol, pat_] :=
Language`ExtendedFullDefinition[sym] =
DeleteCases[Language`ExtendedFullDefinition[sym], pat, Infinity]
Deleting Print[..] as an argument to something other than CompoundExpression may result in errors on execution. For example:
DeleteCases[Hold[Module[{}, Print["Hi there!"]]], _Print,
Infinity] // ReleaseHold
Module::argmu: Module called with 1 argument; 2 or more arguments are expected.
(* Module[{}] *)
Adding a semicolon, Module[{}, Print["Hi there!"];], prevents the error.
Example
Example function to clean up, showing a variety of values (DownValues, SubValues and UpValues):
ClearAll[addto];
call : addto[x_, y_] := (Print["main routine called: ",
HoldForm[call]]; x + y);
call : addto[x_][y_] := (Print["operator form called: ",
HoldForm[call]]; addto[x, y]);
addto /: call :
addto[x_] + y_ := (Print["upvalue form called: ", HoldForm[call]];
addto[x, y]);
Test:
addto[3][4]
operator form called: addto[3][4]
main routine called: addto[3,4]
(* 7 *)
addto[3] + 5
upvalue form called: 5+addto[3]
main routine called: addto[3,5]
(* 8 *)
cleanup[addto, _Print]
(* Language`DefinitionList[HoldForm[addto] -> {OwnValues -> {}, SubValues -> {HoldPattern[call : addto[x_][y_]] :> CompoundExpression[addto[x, y]]}, UpValues -> {HoldPattern[call : addto[x_] + y_] :> CompoundExpression[addto[x, y]]}, DownValues -> {HoldPattern[call : addto[x_, y_]] :> CompoundExpression[x + y]}, NValues -> {}, FormatValues -> {}, DefaultValues -> {}, Messages -> {}, Attributes -> {}}] *)
Test again:
addto[3][4]
(* 7 *)
addto[3] + 5
(* 8 *)
- 235,386
- 17
- 334
- 747
-
How can I generate code from the new defintion? I want to distribute the refactored code to the client. I surmise it would be simple processing of downvalues right? Could you modify your answer to cater for this, please? – Iconoclast Aug 07 '20 at 17:49
-
Also, Micheal, is the Language context documented? What is it about? – Iconoclast Aug 07 '20 at 17:51
-
1@Iconoclast There is no official docs, but here's what we have on site: https://mathematica.stackexchange.com/questions/165843/language-documentation-project – Michael E2 Aug 07 '20 at 18:26
-
To write a notebook should be a process of writing all the definitions, but I don't know how simple it is.
GeneralUtilities`PrintDefinitions@GeneralUtilities`PrintDefinitionswill show you howPrintDefinitionsdoes it. It's a bit more complicated than you need, because it pretties up the result, adding links and tooltips and stripping contexts from the identifiers. I don't think it's an easy thing for me to whip out. -- Also, is it what you want? You and your clients would lose any comments, text and header cells, and so forth that would be in the source notebook. – Michael E2 Aug 07 '20 at 18:37 -
Yes indeed, I just checked saving definitions to a notebook. Nothing fancy here. But I lost all comments and code indentation and formatting. sigh! The text and headers I can add manually, but comments and formatting is just too painful to restore manually. Any pointers on how to proceed? It's exasperating that a system as advanced as MATHEMATICA does not have straightforward mechanisms to accomplish this or else I am missing something. – Iconoclast Aug 07 '20 at 19:48
-
1@Iconoclast Have you tried the notebook editing code I just put at the beginning of my answer? – Michael E2 Aug 07 '20 at 19:52
-
oh sorry just saw that now. Let me check it out. Thanks a bunch in advance. – Iconoclast Aug 07 '20 at 19:54
-
I just looked into manipulating notebooks from the kernel as expressions. There is extensive functionality documented which is (I think) what I need. – Iconoclast Aug 07 '20 at 20:11
That's not a real answer... But I failed to format it as a comment. Again.
You're opening a deep can of worms now, called "metaprogramming".
Please search for "metaprogramming" and discover excellent posts by Leonid Shifrin and others.
In your particular case:
increment = Function[{x}, Print[x]; x + 1]
You can try something like this:
increment //. {
HoldPattern @ CompoundExpression[a___, _Print, b___] :>
CompoundExpression[a, b]}
The original increment has a tree representation:
The result will look like:
Generally speaking you can use rewrite rules to manipulate the structure of the expression (most of the times you will have to inactivate the expression with 'Hold' and friends)
- 1,345
- 6
- 14
-
1Ok, nice. This pretty much serves my purposes. This works for pure functions. How about functions that are defined as modules or blocks? – Iconoclast Aug 07 '20 at 14:46
-
If you're diving into this -- you have to read about metaprogramming in Mathematica in general. Sorry, there're really giants and I'm not prepared to stand on their shoulders.
You can start here, for example: https://mathematica.stackexchange.com/questions/18/where-can-i-find-examples-of-good-mathematica-programming-practice/28073#28073
– Pavel Perikov Aug 07 '20 at 15:01


dbPrintinstead of print. ThendbPrint = Printturns on debugging andUnset[dbPrint]turns it off. You could addSetAttributes[dbPrint, HoldAllComplete]so that arguments won't be evaluated when debugging is off. One could also useBlock[{dbPrint = Print}, myFunc[]]to temporarily turn on debugging. Some internal functions have debugging hooks like this, which is how I got the idea. You could even usedbPrint[tag, msg, ...]so that you could print messages only of classtag. – Michael E2 Aug 07 '20 at 14:51