Redefining print method changes variable scoping in loop

Why does the first loop work but the second does not?

The MWE:

Summary
using Printf
for n in 1:10
	if n == 1
		flag     = true
		idx_last = 1
	else
		flag     = false
		idx_last = ifelse(flag, n, idx_last)
	end

    # Print Progress
	Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
end

for n in 1:10
	if n == 1
		flag     = true
		idx_last = 1
	else
		flag     = false
		idx_last = ifelse(flag, n, idx_last)
	end

    # Print Progress
	# Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
end

I get:

julia> using Printf

julia> for n in 1:10
               if n == 1
                       flag     = true
                       idx_last = 1
               else
                       flag     = false
                       idx_last = ifelse(flag, n, idx_last)
               end
       
           # Print Progress
               Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end

julia> for n in 1:10
               if n == 1
                       flag     = true
                       idx_last = 1
               else
                       flag     = false
                       idx_last = ifelse(flag, n, idx_last)
               end
       
           # Print Progress
               # Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end
ERROR: UndefVarError: `idx_last` not defined
Stacktrace:
 [1] top-level scope
   @ ./REPL[3]:7
1 Like

Anyone have an idea why the first loop does work? My understanding from the docs is that idx_last should not persist into the second iteration. But it does.

1 Like

Attempting to reproduce in two REPL’s each with clean temp environment (Julia v1.9.4).

The first version

(@v1.9) pkg> activate --temp
  Activating new project at `C:\Users\x\AppData\Local\Temp\jl_06Fn89`

julia> using Printf

julia> for n in 1:10
           if n == 1
               flag     = true
               idx_last = 1
           else
               flag     = false
               idx_last = ifelse(flag, n, idx_last)
           end

           # Print Progress
           Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end
ERROR: UndefVarError: `idx_last` not defined
Stacktrace:
 [1] top-level scope
   @ REPL[3]:7

and the second version

(@v1.9) pkg> activate --temp
  Activating new project at `C:\Users\x\AppData\Local\Temp\jl_DNYW0n`

julia> using Printf

julia> for n in 1:10
           if n == 1
               flag     = true
               idx_last = 1
           else
               flag     = false
               idx_last = ifelse(flag, n, idx_last)
           end

           # Print Progress
           # Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end
ERROR: UndefVarError: `idx_last` not defined
Stacktrace:
 [1] top-level scope
   @ .\REPL[3]:7
2 Likes

What version of Julia are you using? I can’t reproduce your all-in-one-REPL example with the provided MWE because I get that error for the first loop.

Thanks, I did try using fresh REPL sessions for each loop but not in a temp project.

I’m using version 1.10.0-rc2 and get the same results:

(@v1.10) pkg> activate --temp
  Activating new project at `/tmp/jl_sQfyMH`

julia> using Printf

julia> for n in 1:10
               if n == 1
                       flag     = true
                       idx_last = 1
               else
                       flag     = true
                       idx_last = ifelse(flag, n, idx_last)
               end
       
           # Print Progress
           Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
           print(idx_last)
       end
12345678910

My versioninfo:

julia> versioninfo()
Julia Version 1.10.0-rc2
Commit dbb9c46795b (2023-12-03 15:25 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 64 × AMD Ryzen Threadripper 2990WX 32-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, znver1)
  Threads: 1 on 64 virtual cores

It also runs using julia --startup-file=no. So not something in the startup.jl file it seems.

1 Like

This seems like a version bug to me, I think it warrants opening an issue. I would leave out the print(idx_last) line if the problem reproduces without it, better to leave out cosmetic factors.

The only guess I have for idx_last persisting is if it somehow mistakenly assigned a global variable that disappears somehow? Pretty far-fetched, but you could throw in a println(n, hasproperty(Main, :idx_last), isdefined(Main, :idx_last)) (first checks if global variable exists, second checks if it is assigned to a value) as the first line in the loop body?

1 Like

So I updated to 1.10.0. I got the same results, then added your line:

julia> using Printf

julia> for n in 1:10
            println(n, hasproperty(Main, :idx_last), isdefined(Main, :idx_last))
           if n == 1
               flag     = true
               idx_last = 1
           else
               flag     = false
               idx_last = ifelse(flag, n, idx_last)
           end

           # Print Progress
           Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end
1falsefalse
2falsefalse
3falsefalse
4falsefalse
5falsefalse
6falsefalse
7falsefalse
8falsefalse
9falsefalse
10falsefalse

julia> for n in 1:10
           println(n, hasproperty(Main, :idx_last), isdefined(Main, :idx_last))
           if n == 1
               flag     = true
               idx_last = 1
           else
               flag     = false
               idx_last = ifelse(flag, n, idx_last)
           end

           # Print Progress
           # Base.show(io::IO, f::Float64) = @printf(io, "%.2f", f)
       end
1falsefalse
2falsefalse
ERROR: UndefVarError: `idx_last` not defined
Stacktrace:
 [1] top-level scope
   @ ./REPL[11]:8

It appears the second loop does iterate twice. Does anyone else see this in Julia 1.10?

1 Like

It is supposed to, the first iteration goes to the unproblematic n==1 branch, the second iteration goes to the else branch where it throws an error over the non-existent idx_last in ifelse(flag, n, idx_last).

You demonstrated that it’s not a temporary global variable at least, so you should omit that println line in the MWE. You could mention that adding such a line would show the REPL’s global scope lacks idx_last at any point.

1 Like

Still happens in the full 1.10.0 release, has an issue been opened on Github for this? BTW you don’t need to define Base.show in particular, even a local foo() = 0 before or after the if statement will cause this.

It got tagged as lowering/bug/parser: