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
quatrepresents a particular type of object. Then the various upvalues forquatspecify "methods" that define howquatobjects 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]
(* {NHoldRest, Protected, ReadProtected} *)
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.
Answer
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.
Comments
Post a Comment