# Permanently extending the behaviour of functions (like decorators)

Suppose I have a function, e.g.

f[x_] := x^2


It works like this:

f[5]
(* 25 *)


I would like to permanently modify it so that calling f[5] will perform some action before and after the evaluation of the original f. For example, I would like f to behave like ff below:

ff[x_] :=
Module[{res},
Print["Before..."];
res = f[x];
Print["After..."];
res
]

ff[5]

During evaluation of In[]:= Before...

During evaluation of In[]:= After...
(* 25 *)


I would like to do this without needing to know what f does, or how it behaves. Preferably, the modification should be carried out automatically. This would be somewhat similar to decorators in Python.

The Villegas–Gayley pattern makes this feasible for built-in functions because built-in definitions always have lower priority than user-issued ones. But with user-defined functions this is much harder to do reliably because we cannot control the ordering of definitions.

Example application:

Print[In ,second, after...];res$], {HoldAll}][ff,{5},Unevaluated[5^2] ] : 25  During evaluation of In[380]:= In first after... (* 25 *)  ### Using pure functions The decorator defined above also allows one to decorate definitions like this: someVariable = Function[args, body]  So for example, the following pf function will behave like the f above: decorator[dec["first"]] @ decorator[dec["second"]] @ pf = Function[x, x^2];  which can be easily checked. ### An example: option validation To add one less trivial example, I would like to show how option validation can be carried out using decorators. I will use this answer as a source of examples, although the validation process described below will be different (will check option values immediately as they are passed, unlike in my answer there, which was checking them once they were computed / used). The following two functions will implement the option-checking logic: ClearAll[checkOptions]; checkOptions[testF_][f_, args___, opts:OptionsPattern[]]:= False =!= Replace[ Flatten[{opts}], (name_ -> val_) :> If[!TrueQ @ testF[f, name, val], Message[OptionCheck::invldopt, name, f, val]; Return[False, Replace] ], {1} ]; ClearAll[OptionCheck]; OptionCheck::invldopt = "Option 1 for function 2 received invalid value 3"; OptionCheck[testFunction_]:= Function[ {f, args, code}, If[!checkOptions[testFunction][f, Sequence @@ args], Return[$Failed],
(* else *)
code
],
HoldAll
];


Basically, OptionCheck takes a test function and generates a decorator, which we can use to validate options. The test function should take the function name, option name, and option value, and return True if the option value is valid and False otherwise.

Let us now define some function:

ClearAll[g];

Options[g]={"a"->1,"b"->False};

decorator[Function[{f, args, code},
Print["The passed arguments were: ", args];code,
HoldAll]
] @
decorator[
OptionCheck[optcheck]
] @
g[x_,y_,OptionsPattern[]]:=
Module[{z=x+y,q,a},a=OptionValue["a"];
q = If[OptionValue["b"],a,a+1];
{x,y,q}
]


and we also need to define optcheck:

ClearAll[optcheck];
optcheck[g, "a",val_]:=IntegerQ[val];
optcheck[g, "b",val_]:=MatchQ[val,True|False];


Now we can test. First, testing the valid inputs:

g[1, 2]
g[1, 2, "a" -> 10]
g[1, 2, "a" -> 10, "b" -> False]


During evaluation of In[392]:= The passed arguments were: {1,2}

(* {1, 2, 2} *)


During evaluation of In[392]:= The passed arguments were: {1,2,a->10}

(* {1, 2, 11} *)


During evaluation of In[392]:= The passed arguments were: {1,2,a->10,b->False}

(* {1, 2, 11} *)


where you can see that the outer decorator was printing the passed arguments.

Now, the input where some options are given values of inappropriate type:

g[1, 2, "a" -> 1.5]


During evaluation of In[395]:= The passed arguments were: {1,2,a->1.5}

During evaluation of In[395]:= OptionCheck::invldopt: Option a for function g received invalid value 1.5

(* $Failed *)  and g[1, 2, "a" -> 1, "b" -> 2]  During evaluation of In[396]:= The passed arguments were: {1,2,a->1,b->2} During evaluation of In[396]:= OptionCheck::invldopt: Option b for function g received invalid value 2 (*$Failed *)


where you can see both decorators at play - the outer one was still printing the passed arguments, while the inner one detected invalid options, issued error message and returned $Failed without executing the body of the function. ## An aside: the role of decorators In python, decorators are very useful. But not because you can modify the behavior of some existing function (because if the function already exists, chances are that you don't own it, but rather import it from some other package). The main usefulness of decorators is that they factor out the logic, which doesn't belong to the function you write, but rather should augment it in some way. For example, perform certain argument checks or checks of the result. Or even add some functionality to your function or class (for example, class decorators can add some methods to the class you define, and can be cheaper alternatives to metaclasses). So, they are useful to add certain behavior and factorize some logic which can be implemented separately, from your main function. The main point is, you typically use decorators on the code you control / write, to have it more concise, and also when certain checks / etc have been already implemented elsewhere and you simply need to decorate your function to enable them. Changing existing definitions is also a well-known practice in python, but it is called monkey-patching. This typically involves changes performed on existing functions or classes, where for example you can import a class and add or modify some of its methods at run-time. This is usually reserved to cases where you have to work around some bug or limitation in code which you use but don't own, and when other softer methods (such as subclassing) are not an option (for example, the authors of the package did not provide necessary hooks). So in Mathematica too, changing definitions of already existing function generally doesn't strike me as a good idea. This is a much more intruding method, and the necessity of doing this means that you perform it on the code that you don't own / control. This can be much more error-prone, you can't easily chain this approach, and I would consider this being the last resort, just like monkey - patching in python. ## Notes The code presented above can surely be further improved and extended. I did not intend to provide a fully bulletproof production - quality implementation, this has rather a status of a proof - of - concept prototype. For example, conditional definitions such as f[arg_]/;condition := rhs won't work with the simple approach above, and there are a few other limitations. Perhaps, at some point the code above could become more robust, and then could actually be used for real applications. • How do you apply this to an already defined function? – Mr.Wizard Sep 12 '17 at 12:50 • @Mr.Wizard You don't. At least, this is not how decorators are used in python. – Leonid Shifrin Sep 12 '17 at 12:52 • Hm... Szabolcs wrote I would like to do this without needing to know what f does, or how it behaves. -- I interpreted this as meaning this kind of method was undesirable. I guess he'll need to clarify that point, at least for me. – Mr.Wizard Sep 12 '17 at 12:55 • @Mr.Wizard The problem is, decorating an already existing function is a much tougher task. In python, decorators act at the function-definition time, where they have access to the function's definition. Also, in python, there is no function overloading, which simplifies things. – Leonid Shifrin Sep 12 '17 at 12:56 • The original motivation came from here: mathematica.stackexchange.com/a/155564/12 The goal was to make sure that a certain context is in the context path while any function from the package is evaluated, but hopefully not have to add code manually to each and every public package function. – Szabolcs Sep 12 '17 at 16:05 A seemingly robust approach seems to be to modify the RHS of DownValues of the symbol you want to decorate. So lets define a decorator function: decorate[f_Symbol] := ( DownValues[f] = Cases[DownValues[f], RuleDelayed[A_, expr_] :> RuleDelayed[A, Module[{result}, Print["Before..."]; result = expr; Print["After..."]; result ] ] ] )  This will modify all DownValue definitions of the symbol with the appropriate wrapper. (It will not work for built in symbols, and functions that are defined through other means e.g. UpValues) We use the Cases statement for the replacement to ensure two things: 1. The RHS of the DownValues are not evaluated during decoration. 2. Only DelayedRule statements at the top level of the DownValues are replaced. Example: Lets define a function f: Clear[f]; f[x_]:=x^2; f[1] = 0; f[] = Null;  We can then add the "decoration" by: decorate@f;  evaluation of f then yields: f[5] During evaluation of In[]:= Before... During evaluation of In[]:= After... (* 25 *)  and f[1] During evaluation of In[]:= Before... During evaluation of In[]:= After... (* 0 *)  and f[] During evaluation of In[]:= Before... During evaluation of In[]:= After... (* Null *)  It seems that argument-less case (g[]) is special and even if you manually reorder DownValues HoldPattern[g[]]:>... will stay on top. Let's assume we don't have such definitions, quite often it will be enough: g // ClearAll g[sym_Symbol] := ToString[sym] Module[{inside = False} , PrependTo[ DownValues[g], HoldPattern[g[sym___] /; TrueQ@Not@inside] :> Block[{inside = True} , Block[{$ContextPath = {}, $Context = "myEmpty"}, g[sym]] ] ] ]; Column @ DownValues[g] g[sym]  HoldPattern[g[sym$___]/;TrueQ[!inside$13949]]:>Block[{inside$13949=True}
, Block[
{$ContextPath={},$Context=myEmpty}
, g[sym$] ] ] HoldPattern[ g[sym_Symbol] ] :> ToString[sym]  "Globalsym"  It wouldn't work if we had a 'pure' V-G trick: Module[{inside = False}, g[sym___] /; TrueQ@Not@inside := Block[{inside = True} , Block[{$ContextPath = {}, \$Context = "myEmpty"}, g[sym]]
]
]

• Literal DownValues (like g[1]) always come before pattern DownValues. The DownValues g[] is treated like a literal DownValues. – Carl Woll Sep 12 '17 at 14:45
• @CarlWoll makes sense, thanks. I wasn;t aware it will be respected when I Set DownValues` manually too, other patterns are not sorted. – Kuba Sep 13 '17 at 7:14