4
$\begingroup$

A while ago I learned a trick which allows one to imitate object-oriented programming in MMA using SubValues:

makeObj[identification_Integer] = Obj[identification];
makeObj[5]
(* Obj[5] *)

These can have "instance fields":

Obj[_]["field"] = 0 (* Default*);

And member functions:

Obj[id_Integer]["increase"] := (Obj[id]["field"]++;)
Obj[5]["increase"];
Obj[5]["field"]
(* 1 *)

However, in order to make an object, I have to identify it in makeObj, otherwise my "field" values will go to the default every time. Is there any implementation which allows me to create objects without explicitly naming the pointers to them?

In other words, can I make the compiler internally assign pointers? In Java, something like:

java.util.ArrayList<myObj> a = new java.util.ArrayList<MyObj>();
for(int i = 0; i < 10; i++)
    a.add(new myObj());
a.get(5).doStuff();

can be easily done, but how would I do it in Mathematica? (without JLink, of course)

If this is not possible, is there some way to have garbage-handling? Like:

Obj["delete", id_] := 
   DeleteCases[SubValues[Obj], HoldPattern[Obj[id][_]] :> _]

(Except this doesn't work)

$\endgroup$
10
  • 1
    $\begingroup$ I'm not familiar with Java. Could you give an example that is Mathematica / English only? $\endgroup$
    – Mr.Wizard
    Oct 20, 2012 at 19:51
  • $\begingroup$ I can't give an example in MMA - that's the problem. What I would like to know is if I can add an Obj to some list, for instance, with a compiler-assigned id. $\endgroup$
    – VF1
    Oct 20, 2012 at 19:53
  • 1
    $\begingroup$ And my problem is I can't understand you. :^) "compiler-assigned id" means little to me. Could you describe the behavior of the conceptual construct? Is Unique[] anything useful? $\endgroup$
    – Mr.Wizard
    Oct 20, 2012 at 19:56
  • $\begingroup$ Yes-Unique[] is something that definitely works. The only issue is that when I did use Unique[] in my actual problem, so many new symbols were created and defined that I can't help but imagine there's a solution which has garbage-handling. $\endgroup$
    – VF1
    Oct 20, 2012 at 19:58
  • 1
    $\begingroup$ This is probably a duplicate of mathematica.stackexchange.com/q/990/5 $\endgroup$
    – rm -rf
    Oct 20, 2012 at 19:59

1 Answer 1

5
$\begingroup$

I don't know a way with subvalues, but you can use Module to create objects without explicit identifier in makeObj:

makeObj[] := Module[{field = 0},
               Switch[#,
                 "increase", field++; #0,
                 "field", field]&]

(Note that I slightly changed your "increase" function to return #0, that is, "self", so I can chain calls for illustrative purposes)

Now you can create anonymous objects:

makeObj[]["increase"]["field"]
(*
==> 1
*)

To see that different calls to makeObj create independent objects, you can try the following:

foo = makeObj[];
bar = makeObj[];
foo["field"]
(*
==> 0
*)
bar["field"]
(*
==> 0
*)
foo["increase"]["increase"];
foo["field"]
(*
==> 2
*)
bar["field"]
(*
==> 0
*)

Since I've noticed that figuring out how to allow method call arguments is not trivial, here's a version which allows to set field with an optional second argument to the "field" method call (and without returning self from "increase"):

makeObj[] := Module[{field = 0},
               Switch[#,
                 "increase",
                   field++;,
                 "field",
                   Module[{args = Hold[##]},
                     If[Length@args > 1,
                       field = args[[2]],
                       field]]]&]

This e.g. allows

foo = makeObj[];
foo["field"]
(*
==> 0
*)
foo["field", 4]
(*
==> 4
*)
foo["field"]
(*
==> 4
*)

Also note that to have the variables garbgage-collected, you have to make sure that there are no references to it; especially a colon at the end will not prevent storing in Out. For example see the following session:

In[2]:= ?Global`*
args    field   makeObj

In[3]:= foo = makeObj[];

In[4]:= ?Global`*
args     args$    field    field$81 foo      makeObj

In[5]:= foo=.

In[6]:= ?Global`*
args     args$    field    field$81 foo      makeObj

In[7]:= Do[bar = makeObj[]]

In[8]:= ?Global`*
args     args$    bar      field    field$81 field$82 foo      makeObj

In[9]:= bar=.

In[10]:= ?Global`*
args     args$    bar      field    field$81 foo      makeObj

For In[2] you see the symbols generated by the definition of makeObj (which I omitted, because it's the same as above). Then I define foo using a semicolon at the end; while this prevents printing, it still assigns the generated object to Out[3] in addition to foo. Therefore the field (field$81) is not removed when foo is cleared in In[5]. On the other hand, the Do in In[7] causes Out[7] to be set to Null, so the object is only referenced by bar. Therefore the corresponding field (field$82) disappears when clearing bar in In[9].

$\endgroup$
2
  • $\begingroup$ Really interesting. One question: Why do you need makeObj[]? Shouldn't just makeObj := .... work? $\endgroup$
    – VF1
    Oct 20, 2012 at 22:16
  • $\begingroup$ @VF1: That's because every execution gives a different object, and there I prefer the function call syntax. Also, it allows you to more easily pass it around without executing it (e.g. if you have a function which takes an object constructor as argument). And finally, you might want to add arguments (e.g. an optional starting value for field), which isn't possible without the function call syntax. $\endgroup$
    – celtschk
    Oct 20, 2012 at 22:57

Your Answer

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

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