5

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

xzczd
  • 65,995
  • 9
  • 163
  • 468
three777
  • 145
  • 7
  • (1) I think you will learn the most if you look at the evaluation process yourself by using Trace! (2) You have to wrap the whole thing inside With with Evaluate (Evaluate[e1; e2]), not each separate statement (Evaluate@e1; Evaluate@e2;) to overcome the fact that With is HoldAll. Otherwise, Evaluate doesn't have the desired effect. – Domen Mar 15 '24 at 11:11
  • 1
    @xzczd There is no reference to Print and that it should affect the evaluation process. – three777 Mar 15 '24 at 11:13
  • 2
    …Please notice ; is also a function CompoundExpression. When Evaluate is inside the CompoundExpression, it no longer breaks through HoldRest attribute of If. – xzczd Mar 15 '24 at 11:17
  • @three777, evaluation process in Mathematica can be tricky at the beginning; I suggest reading Evaluation of Expressions. In your case, you are dealing with two functions that are holding their arguments, namely With and If! That is why you need to escape both of them by two separate Evaluates: With[{a = 1}, Evaluate[If[cond, Evaluate[Print["First possibility:"]; Integrate[int, {x, 0, a}]], Evaluate[Print["Second possibility:"]; Integrate[int, {x, -a, 0}]]]]] – Domen Mar 15 '24 at 11:18
  • But again: I really suggest you closely inspect the output of Trace for your cases! :) It will help you understand better what is going on. – Domen Mar 15 '24 at 11:19
  • 1
    @Domen Have you tried? Your last code prints miraculously both "First possibility:" and "Second possibility:". Which is nonsense. – three777 Mar 15 '24 at 11:32
  • I'm not suggesting you to use Evaluate in last case, I'm just explaining why your last trial fails. – xzczd Mar 15 '24 at 11:47
  • @azerbajdzan, please don't substantially alter the question in the OP's name. You edit is more appropriate as a comment. – Domen Mar 15 '24 at 11:50
  • 1
    @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. – azerbajdzan Mar 15 '24 at 11:56

3 Answers3

4

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:

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}]

xzczd
  • 65,995
  • 9
  • 163
  • 468
  • 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. – three777 Mar 15 '24 at 12:10
  • @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. ) – xzczd Mar 15 '24 at 12:16
4

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 *)

Domen
  • 23,608
  • 1
  • 27
  • 45
3

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[ax, {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[ax, {x, 0, a}], Integrate[a*x, {x, -a, 0}]]]]

(* repeat ) With[ {a = 1}, With[ {cond = a > 0, int = ax}, 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.

lericr
  • 27,668
  • 1
  • 18
  • 64