2
$\begingroup$

It seems that there is an extreme bug in the Block function that has puzzled me a lot today. Below I give two code samples. In the first one can see a bug. In the second one can see that this bug is not even consistent...

r = kx^2 /. {kx -> 2.};
rb := kx^2 /. {kx -> 2.};
r - rb
Block[{kx = 1.}, r]
Block[{kx = 1.}, rb]
r - rb

The output is 0 (as should) 4 (as should) 2 (as shouldn't) 0 (as should)

Now it seems that Block somehow delays replacing kx by 2 in result line 3 and instead puts kx as 1 first.

One might try to argue that this is a feature or the usual blabla. However, This behavior is not consistent. Take for instance the following only very slightly altered code:

r = kx /. {kx -> 2.};
rb := kx /. {kx -> 2.};
r - rb
Block[{kx = 1.}, r]
Block[{kx = 1.}, rb]
r - rb

The output is 0 (as should) 2 (as should) 2 (as should) 0 (as should)

It seems that therefore the Block function has a very strange bug. For reference I am using version 12.0.0.0 in 64bit version.

Does anyone have any insights on how to avoid this bug or have a clear reason for why this happens? I was stumped for 3 hours with code that had strange behavior until I was able to break it down to this issue.

$\endgroup$
  • $\begingroup$ Note that this can get even funkier if you replace the Block function by a summation (iteration here is based on the Block function so there is an issue here). r = kx^2 /. {kx -> 2.}; rb := kx^2 /. {kx -> 2.}; r - rb Sum[r, {kx, 1, 10}] Sum[rb, {kx, 1, 10}] Gives results 40 and 386? $\endgroup$ – Michael Jun 21 at 0:40
  • 3
    $\begingroup$ In rb, kx^2 evaluates to 1. and then {1. -> 2.} replaces this value with 2.. In your second example the same thing happens. I think everything is consistent. $\endgroup$ – Chip Hurst Jun 21 at 0:42
  • 1
    $\begingroup$ Everything is working consistently, although it may appear confusing. Take a look at Trace@Block[{kx = 1.}, rb] to see what's happening under the hood in that case. $\endgroup$ – MarcoB Jun 21 at 0:47
  • $\begingroup$ Yeah, I agree with @Chip. I'm not sure what you think the bug is. Use Trace on Block[{kx = 1}, rb] to see what it's doing and why it makes sense. $\endgroup$ – march Jun 21 at 0:47
  • 1
    $\begingroup$ These examples all indicate the evaluation sequence is working exactly as designed. And it is by no means clear from them what aspect of this design is being claimed as either incorrect or undesirable.It might make sense (as @MarcoB noted) to provide the actual computation of interest along with a clear indication of the expected vs actual result. (Probably best to do in a new post-- this one should be closed.) $\endgroup$ – Daniel Lichtblau Jun 21 at 14:56
10
$\begingroup$

This is not a bug.

Dynamic scoping via Block, Lexical scoping via Module, and Macro replacement via With are sometimes confusing. All have legacies in other programming languages. Having all three in one language is powerful and expressive, but...

Let the variable a contain everything of interest use SetDelayed to give it a "value":

a := {kx, r, rb, s, sb}
r = kx^2 /. {kx -> 2.}; 
s = kx /. kx -> 2.;
a
(* {kx, 4., 4., 2., sb} *)

rb := kx^2 /. {kx -> 2.}; 
sb := kx /. kx -> 2.
a
(* {kx, 4., 4., 2., 2.} *)

r - rb
(* 0. *)

Block effectively saves the original value of kx, uses kx as $1$ everywhere during the execution of the Block, then restores kx. So-called dynamic scoping. So rb2 and sb both go to $2$ via SetDelayed.

Block[{kx = 1.}, a]
(* {1., 4., 2., 2., 2.} *)

a
(* {kx, 4., 4., 2., 2.} *)

Module effectively creates a new variable called (say) kx9857373, initializes it to 1, and uses it in place of kx everywhere kx explicitly appears in the Module. The global kx is not affected.

Module[{kx = 1.}, a]
(* {kx, 4., 4., 2., 2.} *)

a
(* {kx, 4., 4., 2., 2.} *)

With immediately replaces kx by 1 (like a text editor would) everywhere kx appears explicitly in the With body. But it does not appear explicitly.

With[{kx = 1.}, a]
(* {kx, 4., 4., 2., 2.} *)
| improve this answer | |
$\endgroup$
2
$\begingroup$

The problem is not Block, imo, or really even scoping.

The problem is that in kx^2 /. kx -> 2., it seems to be assumed that kx^2 and kx are different. That's not true always (e.g. if kx is set equal to 0, 0., 1, 1., 1. + 0. I,...) and so seems a programmer error. In general, code of the form

f[x] /. x -> a

is inherently buggy, unless the programmer wants f[x] to be replaced by a instead of f[a], when f[x] and x are identical. That seems unlikely.

The issue of rb evaluating to 2. is not a scoping issue, because the evaluation to 2. is going to occur whether kx = 1. is set inside or outside Block. The problem is how to code the replacement operation if the pattern is going to be a global variable.

It's nice to have the MWE provided by the OP, but it's contrived in a way that makes it hard to figure a good suggestion for an actual use-case. Despite the OP's statement that "The issue is not finding a way around it," I think it becomes an issue in light of the preceding analysis.

First approach. Don't use expr /. x -> a if x is a symbol that sometimes will be given a value. There is probably a better way to perform the computation anyway; ask on this site, if you cannot figure it out. Maybe sometimes you have to do it, but you have to think carefully. Later, if someone has to use your code or you reuse your code, one might forget to think through the problems and cause a bug.

Second approach. Maybe one of these does what is desired:

kx^2 /. kx /; ! ValueQ[kx] -> 2.
kx^2 /. x_Symbol /; MatchQ[x, Unevaluated@kx] -> 2.
kx^2 /. x_Symbol /; Block[{kx}, MatchQ[x, kx]] -> 2.
kx^2 /. x_Symbol /; MatchQ[Hold@x, Hold@kx] -> 2.
Block[{kx = 2.}, #] &[kx^2];

Or define a function to substitute the default:

ClearAll[kxSub];
kxSub[expr_] /; ValueQ[kx] := expr;
kxSub[expr_] := expr /. kx -> 2.;

Block[{kx = 1},
 kx^2 // kxSub
 ]
(*  1  *)
| improve this answer | |
$\endgroup$

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