10
$\begingroup$

I would like to be able to define the values of a particular function for new inputs.

f[x1]=f1;
f[x2]=f2;

I would also like to be able redefine the values for most inputs.

f[x1]=f3;
f[x2]=f0;

But there are certain definitions I would like to protect.

f[x0]=f0;

Protect[f] protects all definitions and prevents me from defining the values for new inputs.

I could set/remove/set the Protected attribute for f but that isn't any fun because there are very few definitions I would like protected, and many that get defined and redefined.

My other thought would be to modify Set:

Unprotect[Set]
f::fixeddef="The definition of f for this value cannot be modified"
Set[f[x0],_]:=(Message[f::fixeddef]; $Failed)
SetAttributes[Set,Protected]

This doesn't completely work though:

f[x0]=f1 (* fails appropriately *)
{f[x0],f[x1]}={f3,f4} (* returns {f3,f4} instead of {f0,f4} *)

I assumed Set threads itself over nested lists, recursively calling Set until the lhs is a Symbol, but apprently I assumed wrong.

How does Set work and how can I protect the function definitions only for particular inputs?

$\endgroup$

3 Answers 3

6
$\begingroup$

Ok, I vote for Leonids, but due to confusion, after assuming what he did wouldn't work I thought of a lamer alternative along these lines

SetAttributes[{fix, restore}, HoldAll];
fix[s_Symbol, eqs_] := Module[{guard = True},
  s /; guard := Block[{guard = False}, Null /; restore[s]];
  s /: restore[s] := eqs;]

So

fix[f,
 f[5] = 4;
 f[8] = 23;]

And now

(f[#] = #) &~Scan~Range@10;

f /@ Range@10

(* {1, 2, 3, 4, 4, 6, 7, 23, 9, 10} *)

Edit: ugly alternative in an attempt to "clean it up". Sorry

SetAttributes[{fix, restore}, HoldAll];
Module[{guard},
 SetAttributes[guard, HoldFirst]; _guard = True;
 fix[s_Symbol, eqs_] := (
   s /; guard[s] := RuleCondition@Internal`InheritedBlock[
      {guard}, guard[s] = False;
      restore[s]; Fail];
   s /: restore[s] /; eqs := Null;)
 ]
$\endgroup$
11
  • $\begingroup$ You are illustrating my failure of imagination. This is quite tricky however. :-) $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:16
  • $\begingroup$ I quite like this approach. With effort one could create a situation where the DownValues of f do not show the value you want because f itself was never evaluated, but that seems like quite an edge case. Otherwise I think this method is robust and practical. $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:25
  • $\begingroup$ A new definition (with overhead) is created every time fix is used, even if it's used for the same Set. I'd like to see that cleaned up. We should only ever need one definition for f. $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:29
  • $\begingroup$ @Mr.Wizard, ok, since you liked the proof of concept, I'll remove the "lamer" qualifier and clean that up $\endgroup$
    – Rojo
    Commented Sep 10, 2013 at 23:32
  • $\begingroup$ There is a downside to this: the overhead is higher than I expected. I thought that caching would take place so that the restore would not be done repeatedly but that does not appear to be the case. This slows a simple hash-table function by an order of magnitude. $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:39
4
$\begingroup$

You can do things like that using the black magic associated with Stack, although I would not claim that such tricks are fully reliable. Still:

ClearAll[f];
f::fixeddef = "The definition of f for this value cannot be modified";
f[x0] = 1;
f :=
 (
    Message[f::fixeddef];
    Throw[$Failed]
  ) /; MemberQ[
          Stack[_], 
          HoldForm[f[x0] = _] | HoldForm[{left___, f[x0], right___} = _]
       ]

and now

f[x0] = 2

During evaluation of In[316]:= f::fixeddef: The definition of f for this value cannot be modified

During evaluation of In[316]:= Throw::nocatch: Uncaught Throw[$Failed] returned to top level. >>

(* Hold[Throw[$Failed]] *)

and

{f[x0], f[x1]} = {f3, f4}

During evaluation of In[317]:= f::fixeddef: The definition of f for this value cannot be modified

During evaluation of In[317]:= Throw::nocatch: Uncaught Throw[$Failed] returned to top level. >>

(* Hold[Throw[$Failed]]  *)

The other inconvenience is that you will have to catch the exception in the surrounding code, but, depending on the situation, this may be ok.

$\endgroup$
7
  • $\begingroup$ I was thinking of a stack based approach. But then I started considering using the ownvalue to restore the fixed values instead of prevent the modification. Any ideas on that? (+1) $\endgroup$
    – Rojo
    Commented Sep 10, 2013 at 22:57
  • $\begingroup$ I actually started thinking of that because for a moment I thought that the ownvalues weren't evaluated when using the list version of set, but that made no sense, so +1 again $\endgroup$
    – Rojo
    Commented Sep 10, 2013 at 23:00
  • $\begingroup$ @Rojo This seems problematic. The only freedom you have at the stage when you evaluate the head is to break out, and even then only through exception. Here one would need to somehow abort the current evaluation and still continue the execution. I seem to remember some continuation-like behavior I once managed to implement which seemed to do this sort of things, but right now I don't quite remember where. $\endgroup$ Commented Sep 10, 2013 at 23:00
  • $\begingroup$ I haven't yet become comfortable using Stack[] and friends. I should work on that. Anyway, I see two problems with this: (1) you prevent all assignments in the list form, rather than only the protected one; (2) your pattern is not general enough lists can be nested deeper: your present code fails with {{f[x0]}, f[x1], f[x2]} = {{q}, r, s}; I think #2 is easy to fix, but what about #1? $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:09
  • $\begingroup$ @Mr.Wizard Thanks, good points. As you said, the #2 is fixable by using a more complex pattern. As to #1, I am not sure it needs changing, since obviously such an assignment is a bug / error within this approach, so isolating only that particular assignment might not make much sense. However, one way out would be to explicitly evaluate the rest of assignments before throwing an exception, picking the code for them by destructuring the code taken from the stack. $\endgroup$ Commented Sep 10, 2013 at 23:13
2
$\begingroup$

Great question. I know I'll be thinking about this for days unless someone provides an elegant solution.

I can't think of another approach besides modifying Set. I am uncomfortable with this as it is a basic, low-level function, and because there will be overhead on every Set operation thereafter. One would further need to modify SetDelayed and possibly TagSet etc. Two variations I can think of are:

  • remove f[x0] if it appears on the LHS
  • restore the definition after-the-fact any time f[x0] appears on the LHS

I'll pick the first one. I'll need a dummy variable to assign to and I'll arbitrarily use \[DoubleDagger] as I did for How to ignore list elements when extracting with pattern matching.

Unprotect[Set]

Set[L_, R_] /; ! TrueQ[$setMod] :=
 Block[{$setMod = True},
  Set @@ Join[HoldComplete[L] /. $protected -> ‡, HoldComplete[R]]
 ]

You then define $protected as a pattern for any objects to protect:

$protected = HoldPattern[f[x0]];

Now any assignments made with Set to f[x0] should be silently made to instead.

  • $protected could be defined for multiple objects with: HoldPattern[f[x0] | f[x1]].

  • You can Block $protected (or $setMod = True) to override the protection.

Again, I'm not satisfied with this approach and I'll try to think of a better way.

$\endgroup$
10
  • $\begingroup$ Why not as upvalues? $\endgroup$
    – Rojo
    Commented Sep 10, 2013 at 22:27
  • $\begingroup$ @Rojo UpValues will not run deep enough; they only trigger on expressions at level one. The OP's first example would fail: {f[x0],f[x1]}={f3,f4} $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 22:30
  • $\begingroup$ Maybe we could try modifying Protect instead of Set? $\endgroup$ Commented Sep 10, 2013 at 22:33
  • $\begingroup$ Right, forgot about the list version of Set. It's just that uuugghh, modifing Set aarggh $\endgroup$
    – Rojo
    Commented Sep 10, 2013 at 22:49
  • $\begingroup$ @Timothy I don't see how that would help. AFAIK Protect simply sets the Protected attribute and low-level functionality does the rest. I am not aware of any hooks to affect checking or applying that attribute, or any other for that matter. $\endgroup$
    – Mr.Wizard
    Commented Sep 10, 2013 at 23:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.