7
$\begingroup$

I wrote some Mathematica code that defines a function I want to use repeatedly (f) inside another function (finit) that pre-calculates some of the parameters needed for subsequent evaluations of the inner function. This arrangement is more computationally efficient than putting both steps into one function and it allows for the inner function's argument list to be simpler by focusing only on the parameters that vary from one call to the next.

The arrangement is inspired by and meant to roughly mimic how the corresponding algorithm in C++ is written. In that case I have a class with a constructor (like finit) and a class member function (like f). When a new object of the class is instantiated, a single call to the constructor does all the initialization work whose results are then available to the object every time the member function is called.

Here is a bare bones example to illustrate the basic idea:

finit[x_]:=
 Module[{X},
  X=x;
  Clear[f];
  f[y_]:=X+y;
 ]

In this example, X=x represents the steps that initialize some internal parameters {X} based on the arguments to finit and f is the 'worker' function that combines those internal parameters with its own calling arguments to produce some new result. Every call to finit resets X and redefines f. For example,

finit[a]
f[b]
finit[c]
f[d]

returns

a+b
c+d

I'd like to be able to temporarily change the initialization parameters, run f several times, then go back to the original parameters without having to resupply them to a new call to finit.

This is easy in C++. All I have to do is instantiate two objects using different constructor arguments. Each object has a different name and I can call either one without affecting the other. I can't do that with the Mathematica code as written.

I would be satisfied if the temporary definition were restricted to the inside of a Block or Module.

[Of course, I could make new functions (e.g., ginit[x_] and g[y_]) that do exactly the same thing but I'm looking for an approach that avoids the need to duplicate large blocks of code.]

So far the only way I've found to do this is to put the guts of finit and f into their own (memoryless) functions (e.g., body1 & body2) as shown below:

SetAttributes[body1,HoldAll]
body1[X_,x_]:=(X=x)
body2[X_,y_]:=(X+y)

finit[x_]:=Module[{X},body1[X,x];Clear[f];f[y_]:=body2[X,y]]
finit[a]
{f[b],f[c],f[d]}

Module[{Y,f},body1[Y,α];Clear[f];f[y_]:=body2[Y,y];Times@@{f[β],f[γ],f[δ]}]

{f[b],f[c],f[d]}

Run the above and you get

{a+b,a+c,a+d}
(α+β) (α+γ) (α+δ)
{a+b,a+c,a+d}

which shows that (global) f retains its original definition even though the (local) f inside the second Module is defined differently.

This works, but is kludgy and inefficient. I'm not really satisfied with this solution.

Is there a more elegant way to do this that makes better use of Mathematica's wide ranging capabilities (Blocks, Contexts, etc.), maintains the simplicity and efficiency of the original method, yet avoids verbatim code copying?

$\endgroup$
1
  • $\begingroup$ Kudos to all four (as I write this) authors that provided prompt answers to my question. Each one answers my question in a slightly different way and I learned something new and useful with each answer. I upvoted all four. However, I accepted @lericr's answer because of how simple and lightweight it is. I'm glad I asked for help with this issue. I expect to be able to put more than one of these methods to use many times in future code. $\endgroup$
    – jjoIV
    Commented Oct 10, 2022 at 23:42

4 Answers 4

8
$\begingroup$

One approach would be sub values:

parameterizedF[x_][y_] := x + y

Then, you can do your expensive computation of x to define a curried function:

myF = parameterizedF[a](*where a was some expensive computation*)

Now you just use myF:

myF[y]
(* a + y *)

Instead of a temporary version, you can just define a new function:

tempF = parameterizedF[b];
tempF[y]
(* b + y *)

Or, as you mentioned, you could use Block:

Block[{myF = parameterizedF[b]}, myF[y]]
(* b + y *)

and still retain the original:

myF[y]
(* a + y *)

The pattern you started with can be simplified (I'm not sure what Module buys you here):

ClearAll[myF];
initializeF[param_] := myF[arg_] := param + arg

Then,

initializeF[bbb];
myF[y]
(* bbb + y *)

and

Block[{myF}, initializeF[abc]; myF[x]]
(* abc + x *)

but still

myF[x]
(* bbb + x *)
$\endgroup$
8
$\begingroup$

Object-oriented programming is easy to do in Mathematica using the following paradigm. You can define a class with fields, methods, and constructors. The object and constructor both have the same head, so you don't need a MakeObject function. The key is to have the constructed form have a completely different type signature from the constructor forms.

MyObject[x_Integer] := Enclose @ Module[
    {X},
    X = Confirm @ myExpensiveFunction[x];
    MyObject[<|"X" -> X|>]
]

You can define many constructors like this using different patterns for the input. It is important that the arguments to the constructors be typed, and that their inputs do not match the constructed form. What I mean here is that you can't have a DownValue on MyObject that matches MyObject[<|"X" -> X|>], otherwise you would enter a recursion.

To define a method,

MyObject[ass_Association][f[y_]] := ass["X"] + y

You can define an accessor function for the class fields:

MyObject[KeyValuePattern[x_ -> y_]][x_] := y

and you can use it like this:

myExpensiveFunction[x_] := x^2
obj1 = MyObject[12];
obj2 = MyObject[2];

after creating two objects, call the same method from both:

obj1@f[3]
(* 147 *)

obj2@f[3]
(* 7 *)

or ask the objects for their data:

obj1@"X"
(* 144 *)
$\endgroup$
5
$\begingroup$

Similar to @lericr's SubValues solution. In more complicated cases, I store data in an association attached to a localized variable, especially if I want to update the data dynamically.

See Custom atomic expressions - modern tutorial for using SetValid[] to manage validation of objects.

Below I use one symbol fclass for both the class and object methods. I somewhat abuse String objects with fclass[data_][key_String]... to look up a key in data. This could be written fclass /: "get"[fclass[data_], key_]... (or get instead of "get", but then you should Protect the symbol get).

fclass // ClearAll;
fclass // Attributes = {HoldAll};
Begin["fclassDump`"];
(* class methods *)
fclass["getData", x_] := (* process and validate input *)
 <|"X" -> x|>;
fclass["getData", args___] := $Failed;
fclass["new", args___] :=
  Module[{data = fclass["getData", args]},
   fclass[data] /; FreeQ[data, $Failed]
   ];
(* output formatting *)
MakeBoxes[fclass[data_?AssociationQ], form_] := With[{X = Lookup[data, "X"]},
   MakeBoxes[fclass[X], form]
   ];
(* object methods *)
fclass[data_][key_String] := Lookup[data, key];
fclass[data_][x_] := With[{res = x + Lookup[data, "X"]},
  AssociateTo[data, "LastResult" -> res];
  res];
End[];

Examples:

myF = fclass["new", 4]
(*  fclass[4]  *)

myF // FullForm
(*  fclass[fclassDump`data$31359]  *)

myF[5]
(*  9  *)

myF@"X"
(*  4  *)

myF@"LastResult"
(*  9  *)

myF@"Y"
(*  Missing["KeyAbsent", "Y"]  *)
$\endgroup$
3
$\begingroup$

One could use a lexical closure:

constructor[x0_] := Module[{f,x=x0},
  f[y_] := y+x;
  f
]

Examples:

(* instantiate two objects *)
f1 = constructor[a];
f2 = constructor[c];

f1[b]
(* a+b *)

f2[d]
(* c+b *)

One can do more in the constructor, such as validating x0, or defining more methods for f, and those methods can modify the private variable x. In fact, if one does not do any of that, then there are simpler solutions such as constructor[x0_] := (#+x0)&.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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