Mathematica Asked on October 22, 2021
In my version of object-oriented programming in Mathematica, essentially (without going into details) obj[a]
is a reference to an object whose actual data is stored inside data[a]
. Methods like obj[a][function]
then use and/or modify that data.
Schematically, I define addition of objects using UpValues as follows:
obj[a_] + obj[b_] ^:= add[a, b];
The add
function looks at the values stored in data[a]
and data[b]
, combines them in the desired way, and then stores them in data[ui]
where ui
is a new unique identifier (with i
an integer) and returns obj[ui]
.
Now, here is my problem. Consider a long chain of additions, e.g.
obj[a] + obj[b] + obj[c] + obj[d]
This creates 3 new objects in total, obj[u1]
, obj[u2]
, and obj[u3]
, since each addition of two objects creates a new object. Schematically:
obj[a] + obj[b] + obj[c] + obj[d]
= obj[u1] + obj[c] + obj[d]
= obj[u1] + obj[u2]
= obj[u3]
If many objects are added, this creates an abundance of new objects that I don’t need. After the addition is over, I would like to clear all of them except the last one, which is the actual result of the expression.
Just clearing all of the obj[ui]
for i
less than the current index every time I add two objects won’t work, for two reasons:
obj[c] + obj[d]
, which results in obj[u2]
, clears obj[u1]
, then I won’t be able to add obj[u1] + obj[u2]
to produce obj[u3]
.Therefore, I need to somehow know where the instance of add[a, b]
that is currently running is located in the chain: is it the first, the last, or an intermediate step. If it’s the first, I mark the current i
as the beginning of the objects to delete. If it’s the last, then I delete all of the objects created from that point until i - 1
where i
is the current step.
A simpler option is just to know that the instance is the last step, and then rename the object to something like obj[vi]
where vi
is a new unique identifier and delete all of the obj[ui]
.
Is that possible? If not, what are some other ways in which I can avoid keeping all the objects that were created in the intermediate steps?
EDIT 1:
I am also doing something similar with multiplication, i.e. obj[a_] * obj[b_] ^:= multiply[a, b]
, so the solution will need to incorporate combinations like obj[a] + obj[b]*obj[c]
, which significantly complicates things.
EDIT 2:
The solution I am currently considering is to have the user enclose any operation with some kind of wrapper (e.g. cleanup[ obj[a] + obj[b]*obj[c] ]
) and have cleanup[]
rename its argument to obj[vi]
and delete all the existing intermediate steps obj[ui]
. This makes the syntax slightly less elegant for the user, but doesn’t require any complicated pattern matching.
It would be nice if there was some way to have the wrapper execute automatically when a cell ends its evaluation, or something like that, in which case the user won’t have to worry about the wrapper whenever they make an addition/multiplication operation.
Maybe something along these lines?
obj /: Plus[args : obj[_] ..] := add @@ {args}[[All, 1]];
obj[a] + obj[b] + obj[c]
add[a, b, c]
This way you can write your own multi-argument version of add
that encapsulates all of the internal juggling with objects and then cleans up at the end.
Alternatively, if you want to just apply add
repeatedly to pairs of arguments:
obj /: Plus[args : obj[_] ..] := Fold[add, {args}[[All, 1]]];
obj[a] + obj[b] + obj[c]
add[add[a, b], c]
About your question about mixing Times
and Plus
: You can make a pattern like Plus[args1___, x : obj[_], args2___]
(and similarly for Times
) to interrupt normal evaluation the moment an object is found as an argument for one of these functions. You can then pull out the arguments and inspect them further (see if args1
has Times[___]
in it, for example). However, if you have something like:
Plus[Times[obj[a], obj[b]], Times[obj[c], obj[d]]]
there is no way you can interrupt at the level of Plus
with UpValues
because those can only "pop up" one level. In that case, one of the Times
expressions will first have to resolve to obj[_]
before the Plus
upvalue can fire.
Your suggestion of a cleanup
wrapper could be done like this as well. For example, you could do:
Plus[args1___, x : obj[_], args2___] /; !TrueQ[wrappedQ] := Block[{
wrappedQ = True
},
cleanup[Plus[args1, x, args2]
]
The wrappedQ
variable prevents recursion of this definition. This way you don't need to manually enforce the use of the cleanup
wrapper.
Answered by Sjoerd Smit on October 22, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP