5
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ This is not an answer, but a different way to set up debugging. One could use an unused name, like dbPrint instead of print. Then dbPrint = Print turns on debugging and Unset[dbPrint] turns it off. You could add SetAttributes[dbPrint, HoldAllComplete] so that arguments won't be evaluated when debugging is off. One could also use Block[{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 use dbPrint[tag, msg, ...] so that you could print messages only of class tag. $\endgroup$
    – Michael E2
    Aug 7, 2020 at 14:51
  • 1
    $\begingroup$ Michael, I am aware of this trick and I do use it. But what I intend to achieve is, in a sense, to programmatically refactor (if you will) the original code itself, so that when it is delivered to a customer, he does not see the clutter created by 100's of print statements. $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 14:55
  • $\begingroup$ How are you delivering the code? $\endgroup$
    – Michael E2
    Aug 7, 2020 at 15:13
  • 1
    $\begingroup$ So don't you want the Print statements deleted from the notebook? None of the answers modify the notebook. Or do you have a way to generate a notebook for distribution from the internal definition? $\endgroup$
    – Michael E2
    Aug 7, 2020 at 16:04
  • $\begingroup$ Yes, you are 100% correct (I did not realized this until you pointed it out). But hopefully, the modified definitions could be used to generate the appropriate code? $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 17:47

3 Answers 3

4
$\begingroup$

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  *)
$\endgroup$
8
  • $\begingroup$ 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? $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 17:49
  • $\begingroup$ Also, Micheal, is the Language context documented? What is it about? $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 17:51
  • 1
    $\begingroup$ @Iconoclast There is no official docs, but here's what we have on site: mathematica.stackexchange.com/questions/165843/… $\endgroup$
    – Michael E2
    Aug 7, 2020 at 18:26
  • $\begingroup$ 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`PrintDefinitions will show you how PrintDefinitions does 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. $\endgroup$
    – Michael E2
    Aug 7, 2020 at 18:37
  • 1
    $\begingroup$ @Iconoclast Have you tried the notebook editing code I just put at the beginning of my answer? $\endgroup$
    – Michael E2
    Aug 7, 2020 at 19:52
4
$\begingroup$

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?

$\endgroup$
3
  • $\begingroup$ The "refactoring" part is in DeleteCases[DownValues[myfunction][[1]], fn_[___] /; fn === Print, Infinity, Heads -> True] if you want to copy that definition out. $\endgroup$
    – flinty
    Aug 7, 2020 at 15:02
  • $\begingroup$ 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. $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 15:03
  • $\begingroup$ I am aware of "Suppress Pirint[]s?" question, but I would like to have more than that, i.e., code refactoring as you mentioned. $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 15:05
3
$\begingroup$

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:

enter image description here

The result will look like:

enter image description here

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)

$\endgroup$
2
  • 1
    $\begingroup$ Ok, nice. This pretty much serves my purposes. This works for pure functions. How about functions that are defined as modules or blocks? $\endgroup$
    – Iconoclast
    Aug 7, 2020 at 14:46
  • $\begingroup$ 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: mathematica.stackexchange.com/questions/18/… $\endgroup$ Aug 7, 2020 at 15:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.