1

Mathematica/Wolfram Language newbie here.

The behavior I am after is to be able to "overload" the subscripting of me-defined objects/Heads and thereby use them as if they were Lists.

Example: I have a Blah, called b. I want to be able to b[[2]] and have that call a function that I have defined "on" b with parameter 2, so that I can have custom code return the 2nd part (and likewise for other valid Part specifications, i.e. b[[2;;5]]...).

The "obvious" (to me) thing to try is e.g.:

Part[b_Blah, n_] := ...

but this complains that b isn't a symbol - apparently rather than adding a new rule to the evaluation engine that matches on Part[b_Blah, n_], the system is trying to evaluate b[[n]] and then assign to it (which it cannot, because in this case b has no value and thus the expression is not an assignable symbol).

Apologies if I haven't used the right terminology, but my intention here is to "override subscripting" so that I can e.g. lazy-load subscripted/Part'd expressions for new objects that I create. (To simulate memory-mapping on an array/list, for example.)

Update: See also "part 2", a follow-on to this line of thinking....

  • 2
    I'm sorry that I don't understand exactly what you want to do, but can you not use b[n_] :=...? – Marius Ladegård Meyer Nov 23 '20 at 22:19
  • The other way I can think of is with TagSet or whatever it is called. That way, the definition is attached to Blah and not to Part. – Marius Ladegård Meyer Nov 23 '20 at 22:21
  • 2
    I suggest that you take a look at Association[ ]. – Jagra Nov 23 '20 at 22:29
  • 2
    You can use Blah as a head and Part will act on Blah the same as Part does on List. For example, define b = Blah["first", f[2], "third"]; then evaluate b[[2]]. You get f[2], or whatever you put in the second position. When you evaluate Head @ b, you find that b is a Blah. You can also define b = Blah[ Null, f[2] ] . If you edit your question and include a bit more detail or specific examples of what it is you are trying to do, you will be more likely to get a useful answer. – LouisB Nov 24 '20 at 02:55
  • See also this question/answer on Language`MutationHandler for more advanced custom "objects" – Lukas Lang Nov 24 '20 at 08:37

1 Answers1

2

Well one can 'overload' system functions of Mathematica using UpValues for user-defined symbols like the abstract symbol array in the following:

ClearAll[array];
array/:Part[a_array,i_Integer]:=arrayGetPart[a,i]
array/:Part[a_array,ij_Span]:=arrayGetSpan[a,ij]

which for

array[{1, 2, 3}]
%[[1]]
%%[[1 ;; 2]]

results in calls to the currently undefined getters

array[{1, 2, 3}]
arrayGetPart[array[{1, 2, 3}], 1]
arrayGetSpan[array[{1, 2, 3}], 1 ;; 2]

The user-defined UpValues supersede the default behavior of Part on composite/user-defined objects, which can be disadvantageous in certain scenarios. The overloads or the getters should be further specified to avoid index out of range problems or implement stuff like [[-1]], missing implementations or interpretations for complicated Spans (e.g. m;;n;;l) or multi index versions of Part like [[All,1]]. I would suggest using very specific overloads (i_Integer/;0<i<LenghtOfArray[a]) or getters with rigorous checks on their input parameters to avoid bugs. Stuff like array/:Part[a_array, 0]:=array would also be a good idea to restore some of the default functionality of Part. After all the specific UpValues a plain UpValue like array/:Part[a_array, i_]:=... could be added either throwing some abort/error message or calling an undefined function to avoid undefined/unintended behavior.

Depending on what exactly OP intends to do, this approach might not be the best code solution in Mathematica but it is closed to the requested 'overloading' behavior. I have used similar constructs/'overloads' to construct data structures and wrappers in Mathematica. Following up on the comment of Jagra under OPs question I would also suggest using Association once the data object has an involved internal structure. Something along the lines array[<|"data"->{...}, "length"->n, "rowMajorQ"->True, ...|>] would come to mind. With this internal structure an overload like

ClearAll[array]
array /: Part[array[asoc_Association], i_] := Part[asoc["data"], i]

might be an attractive and highly flexible solution since the data stored in the Association can be extended and has named Keys. One caveat of the UpValue here would be the limitation of the access on the data-values only.

ClearAll[array]
array /: Part[array[asoc_Association], i_] := Part[asoc, i]

would be a more general approach allowing for things like array[<|...|>][["data"]][[1]] and also array[<|...|>][["rowMajorQ"]] which eliminates the need for all the trivial getters and is nice and verbose (given one uses good names for his keys).

N0va
  • 3,370
  • 11
  • 16
  • UpValues get me what I need (a feature I hadn't yet learned about) in terms of directly subscripting the object. Thanks @n0va! –  Nov 26 '20 at 05:35