Basic Overriding

Enhancing built-in procedures and objects avoiding Mathematica's evaluator

The main change introduced with version 2 of the package Summa.m is the ability to address Summa objects with exactly the same input syntax used for built-in Sums. In the rest of this notebook we will address, somewhat loosely, this behavior by "enabling and disabling the Sum evaluation process". The built-in evaluation process is not actually being inhibithed: it just doesn't have a chance to see Sums anymore unless the user sets a specific environment variable that suppresses the conversions of Sum into Summa.

It is possibile to redefine a built-in procedure in such a way that it will call itself without incurring in an infinite recursion. I am giving here just a brief outline of the methods involved, postponing a thorough discussion to a separate notebook ("Overriding built-in objects", whose associated package 'Ovverride.m' is still under development). The key idea is to use an 'environmental variable', say $enablebuiltin, that switches the built-in evaluation process off in order to give way to user defined code. 
We initialize (and protect) the variable to False in order to prevent Mathematica from taking over our code.

\($enablebuiltin = False; 
Protect[$enablebuiltin]; \)

Enhancing built-in procedures

If we wish to redefine a built-in procedure, let's call it BuiltinProcedure,  in term of itself we just have to define it with a conditioned argument that applies only when $enablebuiltin = False and enclose the code that call the original procedure in a Block that sets $enablebuiltin = True. In this way a call to BuiltinProcedure will be passed to the original built-in procedure. Unprotecting and Protecting is necessary in order to allow to modifiy the code associated with the built-in procedure. The following replaces the built-in procedure with a simple call to a user defined prodcedure called userCode

\(Unprotect[BuiltinProcedure]; 

    BuiltinProcedure[args___, opts___Rule\ ] /; $enablebuiltin == 
          False := 
      Block[{$enablebuiltin = True}, 
        
userCode[args, opts]
]; 

    Protect[BuiltinProcedure]; \)

Once we've done this, we can write whichever code is needer for userCode (or we could not. In fact, we migth also want to replace the original object with a wrapper that does just nothing - this is indeed the case for Summa). Since our code is called from withing a Block that sets the environmental variable $enablebuiltin to True, every call to BuiltinProcedure from within it will invoke the procedure originally used by Mathematica. The following sample code can be used to show that.is is so:

userCode[args___, opts___Rule] := 
      
(
        
Print[
          "The variable $enablebuiltin is temporarily ", $enablebuiltin, 
           ". \n The following function calls BuiltinProcedure without any \
condition on the arguments: it is the built-in one." ]; 
        
myFun[BuiltinProcedure[args, opts]]
          
)

Now, when we call the built-in procedure in the global environment (where $enablebuiltin is set to False) we are in fact calling our code that, in turn, can call the original built-in function after having properly set the value of $enable built-in. This is all we need to avoid an infinite recursion problem:

BuiltinProcedure[x, y, {a, b}, myopt -> Automatic]

Incidentally, should we ever need to define a substitute for a built-in procudure in a recursive way, we would have to enclose our code inside a Block that sets the enironmental variable $enablebuiltin=False. We should not forget to provide a test to avoid infinite recursion, of course. We can also recode the builtin procedure in such a way that its arguments could call the redefined function itself. For example, the solution adopted by the package Summa.m, for allowing interpretation of nested Sums is the following:

Unprotect[Sum];
Clear[Sum];
Sum[args__]/;$EnableSum==False:=
	Block[
		{$EnableSum=True},
		Summa[Sequence@@Block[
			{$EnableSum=False},
			tmp[args]
			]
		]
    ]
Protect[Sum];

Applying functions to redefined built-in objects

The aforementioned method can be adopted to attach new definitions to any built-in object Obj. The simplest case is that in which a built-in object is transformed into a user defined wrapper userObj that can be used to ferry unevaluated arguments to other user defined functions userFuncts. This approach will avoid any premature evaluation allowing the user code to  preprocess the arguments to be passed to the original built-in Obj, post-process the result or to produce a final result on its own. It is thus possible to either adapt existing code (by adding options, or further error checking, for example) or to write brand new code from scratch to fully replace the built-in procedure.

A redefined object Obj can appear in the body of user defined functions. By setting the value of the environmental variable $enablebuiltin, the user can either call the built-in object Obj directly or his/her redefined code userObj. For example, if the environmental variable is normally set to False every call to the built-in object will be diverted to the user defined code, but a Block can be used to temporarly redefine $enablebuiltin prior to calling the built-in procedure

userFunct[args_, opts___Rule] := 
      
Module[{}, 
        

  
          
newArgs = f[args]; newOpts = g[opts]; 
        
userResult = BuiltinProcedure[newArgs, newOpts]; 
        

Block[{$enablebuiltin = True}, 
          
  
            
newArgs = h[args]; newOpts = k[opts]; 
          
userResult = BuiltinProcedure[newArgs, newOpts]; 
          
]
]

It is also possible to include the newly redefined object Obj as an argument of userFunct. If the usual evaluation process takes place (that is, unless the function has some holding attribute) and the environmental variable $enablebuiltin is set to False the arguments get evaluated and the user defined fuction will only 'see' the result of the user defined code userObj. If the code is just a wrapper it can be used to extract information that the user has put into what it thought it was the built-in object Obj.
The following sample code shows how to implement this scheme with a function userFunct acting on the redefined built-in object BuiltinProcedure prevously overridden by the user defined code userCode. All we have to pay attention to is to encapsulate the code that needs (if case may be) to call BuiltinProcedure inside a Block enabling built-in evaluation.

userFunct[BuiltinProcedure[args_, opts___Rule], functarg_] := 
      
Block[{$enablebuiltin = True}, 
        
  
          
  
          
Print[
          "$enablebuiltin is temporarily ", $enablebuiltin]; 
        
f[BuiltinProcedure[args, opts]]
]

The message is printed as a side effect of the evaluation of BuiltinProcedure in the arguments of userFunct: This means that there will no longer be signs of BuiltInProcedure in the definition of userFunct, as we can readily see by asking for the definition of userFunct:

?userFunct

The signature matches that of userFunct[BuiltinProcedure, functarg] because both calls will undergo the same evaluation process. Hence BuiltinProcedure and UserProcedure are interchaghable as arguments of userFunct (as long as the environment has $enablebuiltin set to False). In the real world the function f and BuiltinProcedure will produce a result that would avoit too deep a nesting of arguments.

Under certain circumstances, it is even advisable to use userCode directly as an argument for userFunct. This is the case for the Format function, for example, because it would otherwise require to unprotect and protect the built-in object.

userFunct[userCode[args_, opts___Rule], functarg_] := 
      
Block[{$enablebuiltin = True}, 
        
  
          
  
          
Print[
          "$enablebuiltin is temporarily ", $enablebuiltin]; 
        
f[BuiltinProcedure[args, opts]]
]

As an aside we note that if the functions userFuncts are defined in terms of user defined code userCode, they will not apply to the original built-in object when the environmental variable $enablebuiltin is set to False. This facility allows for the cohesistence of two worlds and can be used as a basis for switching on and off the override of built-in functions.

Enabling and disabling built-in evaluation

Suppose we let the user defined code userObj that is intended to replace a built-in object Obj (it could be a wrapper, for example, or a procedure that eventually calls the built-in object) accessible to the global environment. When we set the environmental variable $enablebuiltin to True, we can access either the built-in object 'as is' and the user defined code. In a way we have turned the built-in evaluation for our object on.

If we don't want the user to know about the environmental variable at all, we can write a couple of naive procedures to unprotect and protect the environment variable for her.

\(\(\ EnableObjEvaluation[] := 
      
\(\((
Unprotect[$enablebuiltin]; 
            
$enablebuiltin = True; 
            
Protect[$enablebuiltin]; 
)
            \)

DisableObjEvaluation[] := 
        
\((
Unprotect[$enablebuiltin]; 
          
$enablebuiltin = False; 
          
Protect[$enablebuiltin]; 
)
          \)\)\)\)

Moreover, we can make this process local to a single expression, regardless of the value of the environment variable $EnableObjEvaluation. For example, to locally enable Obj evaluation, we can let a procedure substitute every occurence of userObj with Obj when the builtì-in evaluation is turned on and replacing the surviving Obj with userObj prior to outputting the result in an environment where the built-in Obj evaluaton is disabled. We must give this procedure the attibute Holdfirst to avoid the evaluation of the expression passed

Attributes[EnableObjEvaluation] = {HoldFirst}; 
EnableObjEvaluation[expr_] := 
      
(
EnableObjEvaluation[]; 
        
First[{expr /. userObj -> Obj, 
              DisableObjEvaluation[]}] /. 
          Obj -> userObj
)

Attributes[DisableObjEvaluation] = {HoldFirst}; 
DisableObjEvaluation[expr_] := 
      
(
DisableObjEvaluation[]; 
        
First[{expr /. Obj -> userObj, 
              EnableObjEvaluation[]}] /. 
          userObj -> Obj
)

Here we use First to give back a result after the last element of the list has been evaluated.

Overriding built-in procedures operating on overridden built-in objects *

Version 3 of the package Summa.m will go a further step forward by overriding the built-in functions Simplify, FullSimplify, D, Integrate, Expand, ExpandAll and a few others in order to enable the processing of Summa objects resulting from the redefinition of Sum. It will then be possible to differentiate a Sum by simply passing it to the usual D operator, for example.This is just a cosmetic enhancement that reduces the number of commands needed to manipulate this half new objects.To guarantee backcompability the older names will be retained.
The following predicate will be used to ascertain whether a 'specialized' procedure is to be invoked.

SummaFreeQ[expr_] := FreeQ[expr, Summa, ∞]

The predicate needs to be generalized in order to let it accept a sequence of arguments. This is somewhat needed to automate the generation of all the necessary code.

SummaFreeQ[args__] :=  FreeQ[{args}, Summa, ∞]

\(args = Sequence[pippo, pluto, paperino\  - Summa]; 

    SummaFreeQ[args]\)SummaFreeQ[pippo, pluto, paperino]

This is the enviromental variable used to turn on and off the overriding.

$enableSumSimplify = True;

After unprotecting a built-in procedure we can redefine it in such a way that it will be able to distinguish between arguments that contain Summa objects and arguments that do not. We will apply the user defined procedure only in the first case leaving the ordinary built-in procedure to the rest. FilterOption can be used to give the correct options to each procedure.

Needs["Utilities`FilterOptions`"]

\(Unprotect[Simplify]; 

    Simplify[args__, opts___Rule] /; $enableSumSimplify == True := 
      
Block[{$enableSumSimplify = False}, 
        
If[SummaFreeQ[args], 
          
Simplify[args, FilterOptions[Simplify, opts]], 
          
SumSimplify[args, opts]
]
          
]\)Protect[Simplify];

Simplify[1 + Summa[x^k, {k, 1, ∞}]]

Further work is neeeded in order to let the options of the user defined procedure appeare between those of the built-in procedure. But that is beyond the scope of this notebook and will be covered in a separate notebook named "Overriding.nb", to be released later this century, when the authour will find the time to polish the override.m package.

\(EnableSumProcedure[proc_] := 
      ToExpression[
        StringJoin[{"$EnableSum", ToString[proc], "=True"}]]; 

    DisableSumProcedure[proc_] := 
      ToExpression[
        StringJoin[{"$EnableSum", ToString[proc], "=False"}]]; \)

EnableSumProcedure[Simplify];

$EnableSumSimplify

DisableSumProcedure[Simplify];

$EnableSumSimplify