2
$\begingroup$

Is it possible to write a function in such manner, that it can be either evaluated or left unevaluated?

This is a general question. Sample below is just a sample. No need to find solution for this sample. Need a general solution.

Hypothetical sample:

F[x_] := If[x>=0,
    Sqrt[x],
    Fail[]
];

So that it gives

F[4]
(* ==> 2 *)

But

F[-4]
(* ==> F[-4] *)

I.e. hypothetical function Fail[] stays for "drop evaluation and leave expression unevaluated".

I know I can use Condition

F[x_] := Sqrt[x] /; x >= 0

But I wish to do that in imperative programming way. Is this possible?

UPDATE

I don't understand why this is not working:

F[x_] := Block[{fail},
  (If[x >= 0,
     fail = False;
     Sqrt[x],
     fail = True
     ]) /; ! fail]

UPDATE 1

Manual has a good example

f[x_] := Module[{a}, a = Prime[10^x];
  (FactorInteger[a + 1]) /; a < 10^6]

which is checking internal variables, not arguments. But why my version above not works then?

$\endgroup$
22
  • 2
    $\begingroup$ so this won't do? F[x_?Positive] := Sqrt[x] $\endgroup$
    – chris
    Commented Oct 29, 2012 at 8:31
  • 1
    $\begingroup$ then F[x_] := If[x >= 0, Sqrt[x], Defer[F[x]], Defer[F[x]]] $\endgroup$
    – chris
    Commented Oct 29, 2012 at 9:03
  • 1
    $\begingroup$ @SuzanCioc Could you provide a concise example of your case with double application? $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 9:49
  • 2
    $\begingroup$ @SuzanCioc Your examples does not illustrate the "complex situation" you mention in your comment. You can perfectly well do everything you ask using condition. If you have other cases where condition will not work, you could include it in you example to show why condition is not suitable for you. $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 12:04
  • 2
    $\begingroup$ I don't know why this has been down-voted three times. This is a good question. It may not have been asked in the best way but it would be hard to ask it better without first knowing the answer, IMHO. $\endgroup$
    – Mr.Wizard
    Commented Oct 29, 2012 at 19:16

5 Answers 5

7
$\begingroup$

Your posted code does not work because some of the code you want to evaluate is given as an argument to Condition which holds its arguments, you could make it work by changing this

f[x_] := Block[{ans, success},
    ans = If[x >= 0, success = 1; Sqrt[x], success = 0];
    ans /; (success == 1)
    ]

Notice that the first line of code is process outside of Condition. It might be easier to see if you write it in expression form:

f[x_] := Block[{ans, success},
     ans = If[x >= 0, success = 1; Sqrt[x], success = 0];
     Condition[ans,success == 1]
     ]

Compare this with how your example would be in expression form:

 F[x_] := Block[{fail},
  Condition[If[x >= 0,fail = False;Sqrt[x],fail = True], ! fail]]

You should be able to tell the difference. In the working code the code that sets fail is evaluated before Condition is evaluated. In your code, Condition is evaluated first, which holds the evaluation of the code. This is because condition has Attributes HoldAll.

$\endgroup$
7
  • $\begingroup$ Sorry don't catch the difference between I wrote and you wrote? $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 16:19
  • $\begingroup$ You mean I pass entire answer to condition while you assigned it to a variable first? $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 16:20
  • $\begingroup$ Ok, this looks like a final and sufficient solution I was looking for. It both uses condition and procedural. So I was wrong avoiding condition at all and people were wrong forcing condition in normal form. $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 16:23
  • $\begingroup$ @SuzanCioc I added a comparison. It's because Condition[a=2,b] only evaluates the a=2 part if b is True. So the code you use to calculate fail has to be outside of Condition. $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 16:24
  • 1
    $\begingroup$ @Suzan, try F1[x_] := Module[{tmp = If[x >= 0, Sqrt[x], $Failed]}, tmp /; tmp =!= $Failed] $\endgroup$ Commented Oct 29, 2012 at 16:41
6
$\begingroup$

Yes, it is possible. However, it requires the use of the undocumented System`Private`$Localized, which was (as far as I know) first discovered by @Rojo. This symbol is most likely an internal implementation detail of the evaluator and, being located in a context that is obviously not meant for manipulation by the user, should be approached with caution.

It is, however, quite simple to use:

Clear[F1];
p : F1[x_] := (
  p = System`Private`$Localized;
  If[x >= 0, Sqrt[x], p, p]
 );

We thus have a re-implementation of Condition in terms of If:

F1[2]
(* -> Sqrt[2] *)

F1[-2]
(* F1[-2] *)

F1[a]
(* F1[a] *)

Note that this result lacks the HoldForm/Defer wrappers one requires with other approaches.

$\endgroup$
1
  • 1
    $\begingroup$ This solution is not persistent. When F1[2] is executed twice, it produces $\sqrt{2}$ and F1[2] $\endgroup$ Commented Apr 7, 2020 at 18:55
6
$\begingroup$

A great resource about such stuff is the Tutorial: Package Design. Luckily, it was written by Todd Gayley who is also a registered member of Mathematica.SE.

The important part can be found in section Error Handling

If a more complex test of the input is required, you can put the Condition as the last statement in a Module:

f[x_List] := Module[{result, test},
                    some calculations here...
                    test = true or false depending on calculations
                    result /; test
             ]

If test is set to False, the Condition fails and Mathematica pretends that the match to f[x_List] never happened, moving on to try other rules for f. This is a little-known tidbit of Mathematica programming. Note that Condition must be the last statement in the Module (or Block or CompoundExpression) for it to work.

But this is only a small part of this fine material. For instance, to return an error message, which contains the unevaluated function call, he give the following implementation

Clear[f]
f[a_List] := something
p:HoldPattern[f[a_]] := holycow /; Message[f::list, HoldForm[p], 1]

f[1]
(* f::list: List expected at position 1 in f[1]. *)

This tutorial is definitely worth looking.

$\endgroup$
3
  • $\begingroup$ In searching for the Villegas-Gayley trick (again), I ran across this which I hadn't noticed before. I like the last example, but Literal should be updated to HoldPattern. $\endgroup$
    – rcollyer
    Commented Apr 24, 2013 at 3:22
  • $\begingroup$ @rcollyer yep, Literal is outdated for very long. I don't know why I used it back then. Next time, you can just change it without asking. I trust the system of SE ;-) $\endgroup$
    – halirutan
    Commented Apr 24, 2013 at 3:40
  • $\begingroup$ So do I, but I tend to shy away from changing things on long time users. More out of a courtesy, than anything else. $\endgroup$
    – rcollyer
    Commented Apr 24, 2013 at 11:09
4
$\begingroup$

Using the 4th argument of If should do the trick

F[x_] := If[x >= 0, Sqrt[x], Defer[F[x]], Defer[F[x]]]

{F[2], F[-2],F[a]} 

(* {Sqrt[2],F[-2],F[a]}  *)

or may be

F[x_] := If[x >= 0, Sqrt[x], HoldForm[F[x]], HoldForm[F[x]]]

so that

F[a]/. a-> 2//ReleaseHold

(*  Sqrt[2] *)   

The first form (using Defer) would work also with F[a]/. a-> 2 but you would need to say evaluate it in place, see the documentation

$\endgroup$
11
  • $\begingroup$ This means that both solutions are incomplete since do not fully resemble Condition behavior. $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 9:21
  • $\begingroup$ @SuzanCioc In what way does chris' solution fail? Also I second jVincent's request from above, I have yet to see a reason why condition does not work for you. $\endgroup$
    – sebhofer
    Commented Oct 29, 2012 at 12:15
  • $\begingroup$ @sebhofer I can't post hundreds lines of code here. But you can trust me that I currently use Condition and I dislike it's behavior. $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 13:35
  • $\begingroup$ @SuzanCioc That is all very well, but you won't get any help here by saying: "I don't like the behaviour of Condition so give me something I do like". If you want constructive input here, you need to pose a question which clearly describes the problems you are experiencing and what you eventually want to achieve. Your question, frankly, does not fulfill these criteria. $\endgroup$
    – sebhofer
    Commented Oct 29, 2012 at 13:53
  • 4
    $\begingroup$ @SuzanCioc The downvotes are not due to people not knowing the answer. Trust me on this, this site has some pretty intelligent people. The reason is that you posted a question that reads like this "I want a function that works like List, but I don't like List, so do it without List". You never fully explain why you dislike Condition or what effect it is you are trying to get rid of. Essentially you asked an incomplete question. If you write a small example where the behavior you dislike in Conditions shows up, then people will most likely remove downvotes and be able to provide help. $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 14:35
1
$\begingroup$

I'm going to go out on a limb here and just assume that your problem is that you are nesting conditionals inside your code definitions, and that deeper levels are ignored. If that is the case, what you could do is to use a conditional check to see if your code processed without throwing exceptions, and use exceptions as your Fail[] function.

mysquare[x_] :=
 Module[{n = Catch[

   (*Your code goes here*)
    If[x < 0, Throw[stop]];
    a=Sqrt[x];
    If[x == 7, Throw[stop]];
    a    
    ]},
  n /; ! (n === stop)
 ]

The you can halt the computation at different locations along the code and let it stay in an unevaluated form: mysquare[-2] returns mysquare[-2], mysquare[4] returns 2, and mysquare[7] will evaluate Sqrt[7] but return mysquare[7].

If this is close to what you are actually doing, I would suggest adding some tag to the objects you pass along, that signify that they where already evaluated and didn't change. Otherwise you will end up reassessing that they should remain unevaluated every time you pass them along. This problem can be seen for instance calling Trace[mysquare[7] // Sin] where the calculation needed to assert that mysquare should not evaluate will run twice.

Update

So just for kicks I tried to encapsulate this in a function, but since you need the condtional to wrap the code, I belive that it is not possible to keep a function only on the right hand side to carry this out (Someone correct me please if this can actually be done!). So what I did was create a function that transforms a function definition to include the Exception check condition:

 SetAttributes[noException, HoldAll]
   noException[f_[a_, b_]] /; (f === SetDelayed) := (SetDelayed[a, 
   Module[{n = Catch[b]}, n /; ! (n === stop)]])

Meaning you could carry out the above definition as:

  noException[mysquare[x_]:=(
     If[x < 0, Throw[stop]];
     a=Sqrt[x];
     If[x == 7, Throw[stop]];
     a
   )]

The parenthasis on the right hand of the definition is needed because of the way compound expressions is parsed, but can be omitted with a refined definition:

 noException2[
 CompoundExpression[f_[a_, b_], c__]] /; (f === SetDelayed) := 
  (SetDelayed[a, Module[{n = Catch[CompoundExpression[b, c]]}, n /; ! (n === stop)]])

Which corrects the error if you forget the parenthasis.

$\endgroup$
4
  • $\begingroup$ May be this sort of Condition may be used easier? $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 15:44
  • $\begingroup$ @SuzanCioc What do you mean? $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 15:45
  • $\begingroup$ You are still using a Condition, but in special form. May be it can be used easier, without throwing? See my update. $\endgroup$
    – Suzan Cioc
    Commented Oct 29, 2012 at 15:47
  • $\begingroup$ @SuzanCioc Note that the reason i use throw is to exit the computation as quickly as possible, if for instance you had a very intensive computation If[x<0,a=42;success ==1];Pause[10];a then using the example code you posted, you would always need to compute Pause[10], even when you know early on that success ==0. Using exceptions allow you to short-circuit those cases. $\endgroup$
    – jVincent
    Commented Oct 29, 2012 at 16: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.