9

For cases where one has already assigned DownValues associated with the name 'a', is there an accepted way to block the assignment of OwnValues to the same name? (I originally came across this issue while playing with someone's attempt at implementing a data dictionary.)

Here's what I mean to avoid:

Remove[a];
a[1] := somethingDelayed
a[2] = somethingImmediate;
DownValues[a]
a[1]
a[2]

Returns...

{HoldPattern[a[1]] :> somethingDelayed, 
 HoldPattern[a[2]] :> somethingImmediate}
somethingDelayed
somethingImmediate

And now if we were to evaluate:

a = somethingThatScrewsUpHeads;
(* OwnValues[a] above stored in OwnValues *)
a[1]
a[2]

We get...

somethingThatScrewsUpHeads[1]
somethingThatScrewsUpHeads[2]

Is there an easy/flexible way to prevent OwnValues for any Name in DownValues? (Lemme guess... it's possible, but there's going to be a performance hit?)

telefunkenvf14
  • 1,011
  • 7
  • 19

2 Answers2

13

I don't know if this is an "accepted" way, but you could define a rule that prevents Set and SetDelayed from acting upon a:

Remove[a];
a[1] := somethingDelayed
a[2] = somethingImmediate;

a /: HoldPattern[(Set|SetDelayed)[a, _]] := (Message[a::readOnly]; Abort[])

a::readOnly = "The symbol 'a' cannot be assigned a value.";

With this rule in place, any attempt to assign an OwnValue to a will fail:

In[17]:= a = somethingThatScrewsUpHeads;

During evaluation of In[17]:= a::readOnly:
  The symbol 'a' cannot be assigned a value.

Out[17]= $Aborted

In[18]:= a := somethingThatScrewsUpHeads;

During evaluation of In[18]:= a::readOnly:
  The symbol 'a' cannot be assigned a value.

Out[18]= $Aborted

However, this rule will still allow new DownValues for a:

In[19]:= a[3] = now;
         a[4] := later

In[20]:= a[3]

Out[20]= now

In[21]:= a[4]

Out[21]= later

Performance

The rule does not seem to have an appreciable impact on the performance of Set and SetDelayed, presumably since the rule is installed as an up-value on a. I tried to verify this by executing...

Timing@Do[x = i, {i, 100000000}]

... both before and after the installation of the rule. There was no observable change in the timing. I then tried installing Set-related up-values on 10,000 generated symbols, thus:

Do[
  With[{s=Unique["s"]}
  , s /: HoldPattern[(Set|SetDelayed)[s, _]] :=
      (Message[s::readOnly]; Abort[])
  ]
, {10000}]

Again, the timing did not change even with so many up-value rules in place. These results suggest that this technique is acceptable from a performance standpoint, although I would strongly advise performing performance tests within the context of your specific application.

WReach
  • 18,098
  • 3
  • 49
  • 93
  • Very nice solution - +1. It will however require the user to decide up front which symbols must be "protected" in this way, so is not completely automatic (not sure if complete automation is good in this context though). – Leonid Shifrin Aug 03 '11 at 12:28
  • Arrrg! It kills me to have to choose just one of these excellent answers!! I guess the solution by WReach is probably best given the implementation in UpValues. That said, I think @Leonid provided a unique example of metaprogramming (think that's an accurate description, no?). Never really seen anything like it in MMA before. Oh, and the def[] expression continues to blow my mind---note that he cranked out his original solution in ~16 minutes!! Just filthy. Anyways, serious thanks to both of you for the lesson. – telefunkenvf14 Aug 08 '11 at 12:20
9

I am not aware of any way to directly "block" OwnValues, and since Mathematica's evaluator evaluates heads before anything else (parts, application of DownValues, UpValues and SubValues, etc), this does not bring us anywhere (I discussed this problem briefly in my book).

The problem with a straightforward approach is that it will likely be based on adding DownValues to Set and SetDelayed, since it looks like they can not be overloaded via UpValues.

EDIT

As pointed by @WReach in the comments, for the case at hand UpValues can be successfully used, since we are dealing with Symbols which must be literally present in Set/SetDelayed, and therefore the tag depth 1 is sufficient. My comment is more relevant to redefining Set on some heads, and when expressions with those heads must be allowed to be stored in a variable (cases like Part assignments or custom data types distinguished by heads)

END EDIT

However, adding DownValues for Set and SetDelayed is a recipe for disaster in most cases ( this thread is very illustrative), and should be used very rarely (if at all) and with extreme care.

From the less extreme approaches, perhaps the simplest and safest, but not automatic way is to Protect the symbols after you define them. This method has a problem that you won't be able to add new or modify existing definitions, without Unprotect-ing the symbol.

Alternatively, and to automate things, you can use a number of tricks. One is to define custom assignment operators, such as

ClearAll[def];
SetAttributes[def, HoldAll];
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /; 
    Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} := 
         op[lhs, rhs]

and consistently wrap SetDelayed- and Set-based assignments in def (I chose this syntax for def - kept Set / SetDelayed inside def - to keep the syntax highlighting), and the same for Set. Here is how your example would look like:

In[26]:= 
Clear[a];
def[a[1]:=somethingDelayed];
def[a[2]=somethingImmediate];
def[a=somethingThatScrewsUpHeads];

In[30]:= {a[1],a[2]}
Out[30]= {somethingDelayed,somethingImmediate}

You can then go further and write a code - processing macro, that will wrap SetDelayed- and Set-based assignments in def everywhere in your code:

SetAttributes[useDef, HoldAll];
useDef[code_] := ReleaseHold[Hold[code] /. {x : (_Set | _SetDelayed) :> def[x]}]

So, you can just wrap your piece of code in useDef, and then don't have to change that piece of code at all. For example:

In[31]:= 
useDef[
   Clear[a];
   a[1]:=somethingDelayed;
   a[2]=somethingImmediate;
   a=somethingThatScrewsUpHeads;
]

In[32]:= {a[1],a[2]}
Out[32]= {somethingDelayed,somethingImmediate}

In the interactive session, you can go one step further still and set $Pre = useDef, then you won't forget to wrap your code in useDef.

EDIT

It is trivial to add diagnostic capabilities to def, by using the pattern - matcher. Here is a version that will issue a warning message in case when an assignment to a symbol with DownValues is attempted:

ClearAll[def];
SetAttributes[def, HoldAll];
def::ownval = 
  "An assignment to a symbol `1` with existing DownValues has been attempted";
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /; 
  Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} := op[lhs, rhs]
def[(Set | SetDelayed)[sym_, _]] := 
  Message[def::ownval, Style[HoldForm[sym], Red]];

Again, by using useDef[] (possibly with $Pre), this can be an effective debugging tool, since no changes in the original code are at all needed.

Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Down-values on `Set` are not a good idea, no doubt, but could you please elaborate on why `Set`-related up-values on `a` should be avoided? I read the linked question, but I didn't understand how the objection cited there applies here. – WReach Aug 02 '11 at 23:20
  • @WReach - Thanks to both of you for the interesting ideas! (1) In regards to Leonid's opening paragraph, where he mentions '...not knowing any direct way to block OwnValues...', would it be useful to request a feature like Firewalled[a, {DownValues, ...othersIfWanted...}]? (2) Wondering if I should I have added "and vice versa" to the original question... Thoughts? – telefunkenvf14 Aug 03 '11 at 00:43
  • @WReach I have no objection against `UpValues` for some other symbols involving `Set`, quite the contrary (and I used it a few times myself). You are right - I was following the thought pattern I encountered a few times (overloading `Part` assignment for instance), which does not apply to the case in question. Your solution just did not occur to me. I will edit my post accordingly. – Leonid Shifrin Aug 03 '11 at 12:23
  • 1
    @telefunkenvf14 (1) Regardless of its merits, I am almost certain that such a request won't be fulfilled, since this would require serious changes to the mma evaluator, while the number of users (and use cases) involving low-level things like `DownValues` etc are comparatively small. (2) I think the "vice versa" case can be worked out based on both approaches. Also, I'd use such tools only for debugging anyway - the necessity to use them in "production" would indicate to me a serious design problem. I personally only had these issues when learning mma, but not in any recent times. – Leonid Shifrin Aug 03 '11 at 12:48
  • 1
    @telefunkenvf14 I second @Leonid's response to your comment. If one is concerned about user-added definitions on particular symbols, my advice would be to hide those symbols from the user, e.g. by making them local to a `Module` or private to a package. Then expose functions that operate on those hidden symbols without exposing the symbols themselves. I give this advice as a comment instead of as part of my answer as it goes in a completely different direction from the original question. – WReach Aug 03 '11 at 13:18
  • @Leonid I guess my main interest is in protecting values passed to and from a data dictionary. The scope would be very restricted. Another use might be to prevent headaches for new users the next time I teach---unless the medicine ends up doing more harm than good. :) BTW, your useDef[] is one of the more unique expressions I've ever seen. – telefunkenvf14 Aug 03 '11 at 16:13
  • @telefunkenvf14 Please see my edit - there I show how to add a diagnostic message to `def` (which is really easy), and convert it to a nice (IMO) debugging tool - with `useDef` (and possibly `$Pre`), you don't have to change anything in your code. Regarding `useDef` - this is just one example of things you can do with mma meta - programming. The hard part for making / using such code - processing constructs is to make them composable, so that one can nest them without thinking what may go wrong. There are ways to do it, of course. – Leonid Shifrin Aug 03 '11 at 16:49