5
$\begingroup$

Can you explain the behaviour of the following?

The first With outputs unevaluated If.

So in the second With I added Evaluate before If but then still variable a is unevaluated.

So in third With I added Evaluate even before Integrate and the output is correct 1/2.

But then in the fourth With I added Print at each branch of If which should not affect evaluation in any way yet the output is miraculously a/2, i.e. variable a is not evaluated again.

How to make it work even with the prints?

Clear[int, cond, a]
int = a x;
cond = a > 0;

With[{a = 1}, 
 If[cond, Integrate[int, {x, 0, a}], Integrate[int, {x, -a, 0}]]]

With[{a = 1}, 
 Evaluate@If[cond, Integrate[int, {x, 0, a}], 
   Integrate[int, {x, -a, 0}]]]

With[{a = 1}, 
 Evaluate@If[cond, Evaluate@Integrate[int, {x, 0, a}], 
   Evaluate@Integrate[int, {x, -a, 0}]]]

With[{a = 1}, 
 Evaluate@If[cond, Print["First possibility:"]; 
   Evaluate@Integrate[int, {x, 0, a}], Print["Second possibility:"]; 
   Evaluate@Integrate[int, {x, -a, 0}]]]

enter image description here

$\endgroup$
10
  • $\begingroup$ Does this answer your question? Numerical optimization yields strange result with imaginary number $\endgroup$
    – xzczd
    Commented Mar 15 at 11:06
  • 1
    $\begingroup$ @xzczd There is no reference to Print and that it should affect the evaluation process. $\endgroup$
    – three777
    Commented Mar 15 at 11:13
  • 2
    $\begingroup$ …Please notice ; is also a function CompoundExpression. When Evaluate is inside the CompoundExpression, it no longer breaks through HoldRest attribute of If. $\endgroup$
    – xzczd
    Commented Mar 15 at 11:17
  • 1
    $\begingroup$ @Domen Have you tried? Your last code prints miraculously both "First possibility:" and "Second possibility:". Which is nonsense. $\endgroup$
    – three777
    Commented Mar 15 at 11:32
  • 1
    $\begingroup$ @Domen I just wanted to point out that your suggestion solves one problem but introduces another maybe even more serious one. I do not understand why you canceled my edit since it made the question more clear. You seem to miss that the Print plays a substantial role here not just With and If. $\endgroup$ Commented Mar 15 at 11:56

3 Answers 3

4
$\begingroup$

After a second thought I retract my vote for duplicate because the relevance is probably not so obvious at least for beginners, but do remember this post is essentially duplicate of the following:

Why doesn't b = a; f[a_] := b; f[2] return 2?

Numerical optimization yields strange result with imaginary number

Understand that semicolon (;) is not a delimiter

In short:

  1. as in int and cond are not explicit i.e. they hide inside int and cond so With cannot see them.

  2. Evaluate is a function that breaks through evaluation control of HoldAll, HoldFirst, HoldRest, etc. but as mentioned in the document of Evaluate: "Evaluate only overrides HoldFirst etc. attributes when it appears directly as the head of the function argument that would otherwise be held."

  3. With and If both have attribute for evaluation control. With has HoldAll, If has HoldRest.

  4. Code like Print["First possibility:"]; Evaluate@Integrate[int, {x, 0, a}] won't work in If, because ; is also a function whose FullForm is CompoundExpression, so the Evaluate won't break through the evaluation control of If.

Here're 3 possible fixes for your last With[…], in order of increasing difficulty for digesting:

Clear[int, cond, a]
int[a_] = a  x;
cond[a_] = a > 0;

With[{a = 1}, 
 With[{cond = cond[a], int = int[a]}, If[cond, Print["First possibility:"];
   Integrate[int, {x, 0, a}], Print["Second possibility:"];
   Integrate[int, {x, -a, 0}]]]]

Clear[int, cond, a]
int = a  x;
cond = a > 0;

With[{a = 1}, If[#, Print["First possibility:"];
    Integrate[#2, {x, 0, a}], Print["Second possibility:"];
    Integrate[#2, {x, -a, 0}]]] &[cond, int]

Clear[int, cond, a]
int = a  x;
cond = a > 0;

Unevaluated@With[{a = 1}, If[cond, Print["First possibility:"];
    Integrate[int, {x, 0, a}], Print["Second possibility:"];
    Integrate[int, {x, -a, 0}]]] /. Flatten[OwnValues /@ Unevaluated@{cond, int}]
$\endgroup$
2
  • $\begingroup$ I like the second variant which is concise and easy to read although I do not understand why this should work just because arguments are taken from outside of With. $\endgroup$
    – three777
    Commented Mar 15 at 12:10
  • $\begingroup$ @three777 It's just a more targeted evaluation control. If a function doesn't have attribute like HoldAll, its argument(s) will evaluate before going into the function. (Remember & is just shorthand for Function. ) $\endgroup$
    – xzczd
    Commented Mar 15 at 12:16
4
$\begingroup$

Evaluation process in Mathematica can be tricky at the beginning; I suggest reading the tutorial on the Evaluation of Expressions.

The problem you are facing is that both With and If hold their arguments (With is HoldAll, and If is HoldRest). In order to circumvent this, you can wrap the argument with Evaluate.

Let's begin with simpler examples:

cond = a > 0;
With[{a = 1}, cond]
(* a > 0 *)

Why did With not change the value of a? Because, roughly speaking, With replaces only symbols it can lexically explicitly "see"! But it only sees cond, because the evaluation of cond to a > 0 is postponed due to HoldAll. If you cirvument this, cond is evaluated before With does its magic:

With[{a = 1}, Evaluate[cond]]
(* True *)

You can more closely inspect all of this by using Trace.

With[{a = 1}, cond] // Trace
(* {With[{a=1}, cond], cond, a>0} *)

With[{a = 1}, Evaluate[cond]] // Trace
(* {{cond, a>0}, With[{a=1},a>0], 1>0, True} *)

See the difference?

Now to adding If:

int = a x;

With[{a = 1}, If[cond, int]]
(* If[a>0,int] *)
With[{a = 1}, Evaluate[If[cond, int]]]
(* a x *)

The first output is expected: With didn't see any a to be replaced, so the output is an undecided If. But what's with the second output? Look at the Trace:

With[{a = 1}, Evaluate[If[cond, int]]] // Trace
(* {{{cond,a>0},If[a>0,int]},With[{a=1},If[a>0,int]],If[1>0,int],
    {1>0,True},If[True,int],int,a x} *)

Because If holds its arguments (except the first one), they get evaluated only after With has done the replacement! So you have to now also circumvent this holding inside If with another Evaluate:

With[{a = 1}, Evaluate[If[cond, Evaluate[int]]]]
(* x *)

Now let's add Prints:

With[{a = 1}, 
 Evaluate[
  If[cond, 
     Evaluate[Print["1st"]; int], 
     Evaluate[Print["2nd"]; int]
]]]
(* 1st *)
(* 2nd *)
(* x *)

Whoops? Why did this happen? Well, this is exactly the reason why If holds its arguments! You do not want to evaluate both branches before you now which one is the correct one! However, by using Evaluate, we evaluated both of them, so both Prints occurred. What can you do now? Well, many different options:

  1. Ugly nested Withs
With[{a = 1}, Evaluate[With[{int = int}, If[cond, Print["1st"]; int, Print["2nd"]; int]]]]
(* 1st *)
(* x *)
  1. or more elegantly, define int and cond as functions
Clear[cond, int];
cond[a_] := a > 0;
int[a_] := ax;

With[{a = 1}, If[cond[a], Print["1st"]; int[a], Print["2nd"]; int[a]]]
(* 1st *)
(* x *)
$\endgroup$
3
$\begingroup$

Late to the game, but I feel like there is some pedagogical value to be added.

Let's start with the basic setup:

int = a x;
cond = a > 0;
With[{a = 1}, If[cond, Integrate[int, {x, 0, a}], Integrate[int, {x, -a, 0}]]]
(* result was the unsimplified If that you didn't want *)

What was the purpose of setting up the situation this way? What were you hoping to achieve? My guess is that you wanted to extract out repeated chunks of code or chunks that maybe you wanted easier control over (the int and cond). Then, you wanted to sort of "parameterize" the situation (the a=1). If that's correct, then you simply chose the wrong "localizing" construct. What With does is to replace symbols that appear explicitly in the body. It's just a simple lexical operation. But what you wanted was to temporarily give a value to a "global" symbol. The construct for that is Block:

int = a  x;
cond = a > 0;
Block[{a = 1}, If[cond, Integrate[int, {x, 0, a}], Integrate[int, {x, -a, 0}]]]
(* 1/2 *)

Now, as several responses have indicated, you also could have "functionalized" your situation rather than relying on "global variables" and side-effects. I won't reiterate those.

There is a case for using With, and that's when you want a sort of purely side-effect free little "experiment" (for lack of a better word). It might be easiest to see by building it up bit by bit:

(* starting expression where everything is explicit *)
If[a > 0, Integrate[a*x, {x, 0, a}], Integrate[a*x, {x, -a, 0}]]

(* choose something to abstract out, maybe the a *)
With[{a = 1}, If[a > 0, Integrate[a*x, {x, 0, a}], Integrate[a*x, {x, -a, 0}]]]

(* choose another thing to abstract, but respect the dependency by nesting the With *)
With[
  {a = 1},
  With[{cond = a > 0}, If[cond, Integrate[a*x, {x, 0, a}], Integrate[a*x, {x, -a, 0}]]]]

(* repeat *)
With[
  {a = 1},
  With[
    {cond = a > 0, int = a*x}, 
    If[cond, Integrate[int, {x, 0, a}], Integrate[int, {x, -a, 0}]]]]

Every step in this transformation "worked"--you could have stopped (or continued on) whenever you wanted. Also, the symbols a, int, and cond are now not polluting your "global namespace", which reduces the risk of side-effects and name collisions. You also have a nice little setup where you can easily change the value of a, cond, or int independently to see what happens.

$\endgroup$

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.