3

I would like to overload Mathematica's Set function (=), which turns out to be too tricky for me (see following code example). I successfully overloaded other functions (e.g. Reverse in the code example). Any suggestions?

In[17]:= ClearAll[struct];

In[18]:= var1=struct[{1,2}]
Out[18]= struct[{1,2}]

In[19]:= Reverse@var1
Out[19]= struct[{1,2}]

In[20]:= Head[var1]
Out[20]= struct

In[21]:= struct/:Reverse[stuff_struct]:=struct[Reverse@stuff[[1]]]

In[22]:= Reverse@var1
Out[22]= struct[{2,1}]

In[23]:= struct/:Set[stuff_struct,rhs_]:=Set[struct[[1]],rhs]

In[24]:= var1="Success!"
Out[24]= Success!

In[25]:= var1
Out[25]= Success!

In[26]:= Head[var1]
Out[26]= String

In[27]:= ??struct
Global`struct
Reverse[stuff_struct]^:=struct[Reverse[stuff[[1]]]]

(stuff_struct=rhs_)^:=struct[[1]]=rhs
phantomas1234
  • 470
  • 3
  • 13

2 Answers2

8

I don't think that what you want can be done with UpValues (alas), since the symbol (tag) must be not deeper than level one for definition to work. Also, the semantics you want is somewhat unusual in Mathematica, since most Mathematica expressions are immutable (not L-values), and their parts can not be assigned values. I believe that this code will do something similar to what you want:

Unprotect[Set];
Set[var_Symbol, rhs_] /; 
   MatchQ[Hold[var] /. OwnValues[var], Hold[_struct]] := Set[var[[1]], rhs];
Protect[Set];

For example:

In[33]:= var1 = struct[{1, 2}]

Out[33]= struct[{1, 2}]

In[34]:= var1 = "Success!"

Out[34]= "Success!"

In[35]:= var1

Out[35]= struct["Success!"]

But generally, adding DownValues to such important commands as Set is not recommended since this may corrupt the system in subtle ways.

EDIT

Expanding a bit on why your attempt failed: Mathematica implements flow control and assignment operators using the mechanism of argument holding (Hold* - attributes, described here). This mechanism allows it to, in particular, imitate pass-by-reference semantics needed for assignments. But then, at the moment when you assign to var1, Set does not know what is stored in var1 already, since it only has the symbol var1, not its value. The pattern _struct does not match because, even if the variable already stores some struct, Set only has the variable name. For the match to be successful, the variable inside Set would have to evaluate to its value. But then, the value is immutable and you can not assign to it. The code I suggested tests whether the variable has an assigned value that is of the form struct[something], and if so, modifies the first part (the Part command is an exception, it can modify parts of an L-value expression provided that those parts already exist).

You can read more on the topics of Hold* - attributes and related issues in many places, for example here and here

Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Wow, that was fast. I am also not feeling comfortable messing around with builtin functionality. Not being able to use UpValues in this case makes it especially unattractive. Thank you Leonid – phantomas1234 May 04 '11 at 16:30
  • I think the sentence _But generally, adding DownValues to such important commands as Set is not recommended since this may corrupt the system in subtle ways._ is clear enough. Nice work, Leonid (as always) – Dr. belisarius May 04 '11 at 16:36
  • @phantomas1234 You can still make things look OOP-ish, for example you can define methods for `struct`, using `UpValues` to redefine `Dot` on `struct`. I give a simple example in my post in this thread: http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/ec4958c35f99758d/ – Leonid Shifrin May 04 '11 at 16:43
  • @belisarius Thanks, though this probably could be explained better than I did it here. I will edit this post if/when I manage to make a better explanation. – Leonid Shifrin May 04 '11 at 16:45
  • @belisarius Yeah, I know. That is exactly what I am doing right now for an easy-to-use modeling framework. Works like a charm. Would have been nice to also get something like struct["ID"] = "NewID" to work, though. – phantomas1234 May 04 '11 at 16:57
  • Leonid, congratulations on 4,000 rep! – Mr.Wizard May 05 '11 at 07:37
2

I also do not believe that this can be done with TagSet, because the first argument of Set must be held.

It seems to me that if modifying Set, it can be done with:

Unprotect[Set]

Set[s_, x_] /; Head[s] === struct := s[[1]] = x

However, Leonid knows Mathematica better than I, and he probably has a good reason for the longer definition.

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • 2
    I wanted to add exactly this code in another edit, but was too lazy, because it would need another paragraph of explanations. The real reasons that I did not start with it are these: first, you may trigger unwanted side effects by evaluating `s` - while in my code, `s` is analyzed but not evaluated. Second, in this general code `s` may not represent an L-value (assignable expression), for example like in this case: `struct[stuff] = value`, and the resulting error messages etc may be confusing to the user. – Leonid Shifrin May 05 '11 at 09:20
  • @Leonid thank you for your explanations. I learn a lot from them. – Mr.Wizard May 05 '11 at 09:36