Experiment with TagSetDelayed involving Part

Reading Associating Definitions With Different Symbols, I was intrigued by this:

You can think of upvalues as a way to implement certain aspects of object-oriented programming. A symbol like quat represents a particular type of object. Then the various upvalues for quat specify "methods" that define how quat objects should behave under certain operations, or on receipt of certain "messages".

To play with this idea, I attempted to implement a variation on List called ContiguousOrderedIntegerMap: like List, but any attempts to get an index beyond its length return Null; and any attempts to set an index beyond its length automatically fill intermediate indices with Null. I started,

ClearAll[ContiguousOrderedIntegerMap, foo, bar];
SetAttributes[ContiguousOrderedIntegerMap, HoldAll];
ContiguousOrderedIntegerMap /:
Part[ContiguousOrderedIntegerMap[expr_], i_] := (
If[Length@expr < i, expr = PadRight[expr, i, Null]];
return = expr[[i]];
expr = ContiguousOrderedIntegerMap@expr;
return
);
foo = {100, 200, 300};
bar = ContiguousOrderedIntegerMap@foo;
Print@bar;
Print@bar[[2]];
Print@bar[[5]];

(* ContiguousOrderedIntegerMap[foo] *)
(* 200 *)
(* Null *)


This is however, of course, wrong, because I'm mistakenly modifying foo instead of bar and creating nonsense recursion, i.e.

foo = ContiguousOrderedIntegerMap[foo];


So, the first thing I'm stuck on is, How can I access the symbol passed into Part? Scanning through all Mathematica's available operators, I came across Pattern (:), which seemed like exactly what I needed, and applied it like so:

ContiguousOrderedIntegerMap /:
Part[symbol : ContiguousOrderedIntegerMap[expr_], i_] := (
Print@symbol;

(* ContiguousOrderedIntegerMap[foo] *)


But wait, instead of bar, I got back bar's OwnValue. Why? Ah, because Part evaluates its first argument:

Attributes[Part]



So, what's next? Do I find some way to set HoldFirst on Part only when applying to a ContiguousOrderedIntegerMap? (If that's even possible.) Instead, is there a more obvious, idiomatic way to go about this all?

I have other questions in mind, e.g. "How do I set a separate UpValue for Set given that attempting to TagSet Part[Set[...]] would push ContiguousOrderedIntegerMap a level too deep?". However, I end my question here for now, since it's where I'm stuck, and I'll probably need to edit this question a few times as I receive help and make progress.

• Even if you got the HoldFirst working somehow, what would you do for a ContiguousOrderedIntegerMap that is not bound to any variable, like one that is returned by a function?
– VF1
Jan 30, 2014 at 23:26
• One way to get around the stateless expressions and value semantics of Mathematica is by relying on function definitions to relate to individual objects. In my question here, you can create an object which has a SubValue or DownValue that points to the actual list/data. Then your TagSet would copy the "function" - really, the object - by value, and it could alter the down/subvalue accordingly.
– VF1
Jan 30, 2014 at 23:33
• Also, by the time the UpValue from the integer map evaluates, bar would have already been expanded into its OwnValue. All information regarding the symbol would have been lost. I don't think TagSet is the solution here, unless the value the equivalent of some kind of pointer to its data as I described above.
– VF1
Jan 31, 2014 at 0:02
• @VF1 - Sorry for the late response; I was AFK. In answer to your first comment, if I understand you correctly: if not bound to any variable, I'd want whatever happens when you try to do, for example, ConstantArray[0, 10][[2]] = 7—I'd expect an error, e.g. "[...] is not a symbol." Jan 31, 2014 at 5:24
• In any event, see my answer (and the two links I provided) regarding the read part of this question. Regarding the writing - the part-set - that will not be possible, at least using the built-in set, for reasons I have mentioned.
– VF1
Jan 31, 2014 at 6:59

I don't understand why you need this functionality. If you really need Part to write to your object, then again, I suggest you use one of the ways of simulating OOP in MMA (see my linked question in comments). You ask if instead there is a simpler, more obvious approach.

I think that all you really need is the following:

ContiguousOrderedIntegerMap /:
Part[ContiguousOrderedIntegerMap[expr_], i_] /;
Length@expr < i := Null
ContiguousOrderedIntegerMap /:
Part[ContiguousOrderedIntegerMap[expr_], i_] := expr[[i]]
map = ContiguousOrderedIntegerMap@{1, 2, 3};
map[[1]]
(* 1 *)
map[[4]] // ToString
(* Null *)


There's no need to resize your list when you attempt to read out of range - the results will be the same either way.

When setting, however, I can see why one might want to pad your list. However, with setting this problem will not appear since when you set you are guaranteeing that the lhs is a symbol that a value can be bound to. As described here, you will have to use a custom set function in order to have part-setting like list[[4]]=4. However, you can set this custom function's attributes to be HoldFirst and avoid the previous issue.

• I'm a little confused that you seem to believe it's not "really" necessary for Part to write to my object—I'm trying to inherit Part's normal behavior, and normally when you do a = {1, 2, 3}; a[[2]] = 7; a, you indeed get {1, 7, 3}—so my question is, how do I preserve that behavior? (I'm now testing your answer—thank you!) Jan 31, 2014 at 7:12
• @acheong87 For reading purposes only (that is, using Part to access elements of the list without Set) you don't need to expand your list, as I mentioned, unless you had some reason to "really" need to (which I did not see). However, for making a part-set, which is a very specific built-in construct that applies only to lists and expressions, you cannot define behavior relying only on UpValues of Set, since the expression of the symbol is nested too deep. Instead you have to rely on your own MySet or do what I posted.
– VF1
Jan 31, 2014 at 7:16
• Oh! So it was I who misunderstood you; sorry. That makes complete sense, and it's funny because one of my approaches before even posting this question was creating a PartSet function. Thanks, I think this will work! Jan 31, 2014 at 7:18
• @acheong87 No problem. Alternatively, there may be a solution that involves holding your data directly in your function MyList[a,b,c]. Then the default part-set will replace accordingly since it will work on expressions; however, I don't see an immediate way of getting this approach to allow for resizing.
– VF1
Jan 31, 2014 at 7:21