Protecting function definitions only for particular inputs

8

4

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?

Timothy Wofford

Posted 2013-09-10T21:20:19.510

Reputation: 3 713

Answers

5

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;)
 ]

Rojo

Posted 2013-09-10T21:20:19.510

Reputation: 40 993

@Rojo, I haven't abandoned this question. Your example gives the default behavior of passing the rhs on to the next calculation and effectively not changing the protected values. But it doesn't send a warning. @Leonid's solution only works against changes caused by Set. I recently came across ValueFunction in the Experimental context, which may show some promise. – Timothy Wofford – 2013-10-20T20:44:11.207

@TimothyWofford as far as I know ValueFunction is for OwnValues only, but my memory may be wrong – Rojo – 2013-10-20T20:52:16.063

You are illustrating my failure of imagination. This is quite tricky however. :-) – Mr.Wizard – 2013-09-10T23:16:25.813

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. – Mr.Wizard – 2013-09-10T23:25:19.710

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. – Mr.Wizard – 2013-09-10T23:29:37.510

@Mr.Wizard, ok, since you liked the proof of concept, I'll remove the "lamer" qualifier and clean that up – Rojo – 2013-09-10T23:32:00.837

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. – Mr.Wizard – 2013-09-10T23:39:05.033

@Mr.Wizard look what happens when I try to clean up. I gave up on trying not to use non-built-ins. Feel free to edit. – Rojo – 2013-09-10T23:43:49.487

Now the definitions don't get accumulated, but the equations do get all accumulated – Rojo – 2013-09-10T23:45:28.963

@Mr.Wizard as to the speed I wasn't too hopeful from the start, but perhaps there's hope? – Rojo – 2013-09-10T23:46:40.860

You shouldn't be apologizing for your avant-garde code. This is great stuff. Anyway, you seem to have a better handle on caching than I do; after all guard is your trick. It would be nice if there was some way that f would only be re-evaluated when a change was made that affected the value of f, but I don't know how to go about it. – Mr.Wizard – 2013-09-11T00:15:35.687

4

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.

Leonid Shifrin

Posted 2013-09-10T21:20:19.510

Reputation: 108 027

@Leonid Shifrin I agree with Leonid on both points. – Timothy Wofford – 2013-09-11T09:14:55.260

@Mr.Wizard However, there is that sticking point of writing a "more complex pattern" which makes point #2 "easy to fix". Would anyone care to fill in this gap? – Timothy Wofford – 2013-09-11T09:15:37.450

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) – Rojo – 2013-09-10T22:57:05.950

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 – Rojo – 2013-09-10T23:00:38.037

@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. – Leonid Shifrin – 2013-09-10T23:00:55.407

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? – Mr.Wizard – 2013-09-10T23:09:22.970

@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. – Leonid Shifrin – 2013-09-10T23:13:40.423

2

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.

Mr.Wizard

Posted 2013-09-10T21:20:19.510

Reputation: 259 163

Why not as upvalues? – Rojo – 2013-09-10T22:27:23.503

@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} – Mr.Wizard – 2013-09-10T22:30:35.183

Maybe we could try modifying Protect instead of Set? – Timothy Wofford – 2013-09-10T22:33:53.633

Right, forgot about the list version of Set. It's just that uuugghh, modifing Set aarggh – Rojo – 2013-09-10T22:49:03.300

@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. – Mr.Wizard – 2013-09-10T23:02:48.710

@Rojo I know; like I said this is a great question because it is both practical and challenging. – Mr.Wizard – 2013-09-10T23:02:54.303

@Mr.Wizard, I think it's very hopeful to expect solutions without drawbacks, so sorry for not giving the instant +1. Leonid's probably can be fixed to address your issues, but it will never work inside StackInhibited. We'll have to wait until they give us some form of Experimental`ValueFunction that works on downvalues – Rojo – 2013-09-10T23:14:34.833

Could something be done with $Pre or $PreRead to detect the relevant Set expressions and modify them appropriately? The idea being to avoid monkeying with the definition of Set. – m_goldberg – 2013-09-10T23:27:26.530

@m_goldberg Perhaps, but they won't work inside packages, and also, some code may be dynamically generated - and thus undetected by these functions. – Leonid Shifrin – 2013-09-10T23:29:16.813

@m_goldberg What Leonid said; believe me I thought of that first because I didn't want to modify Set. I think Rojo has the best approach so far; it just needs a bit of refinement. – Mr.Wizard – 2013-09-10T23:31:06.767