Scope of variable

This code runs as expected when let block is uncommented:

using AbstractPlotting, GLMakie
using GLMakie.GLFW
using GLMakie: to_native

# let 
  npoints = 10
  points = Node(Point3f0.(rand(npoints), rand(npoints), rand(npoints)))
  scene = Scene(limits = FRect3D(Vec3f0(0), Vec3f0(1)), resolution = (1200, 1200), camera = cam3d!, show_axis = false)
  scatter!(scene, points)
  window = to_native(display(scene)) 
  GLFW.SetWindowTitle(window, "Test $npoints")

  on(events(scene).keyboardbuttons) do key
    ispressed(key, Keyboard.escape)  &&  GLFW.SetWindowShouldClose(window, true) 
    ispressed(key, Keyboard.page_down)  &&  (npoints = max(2, npoints-1))
    ispressed(key, Keyboard.page_up)  &&  (npoints += 1)
    GLFW.SetWindowTitle(window, "Test $npoints")
    points[] = Point3f0.(rand(npoints), rand(npoints), rand(npoints))
  end
# end

but when it is commented, I’m getting:

UndefVarError: npoints not defined

Why is that?

Global variables cannot be changed in do blocks?

https://github.com/JuliaLang/julia/issues/28789

do is just syntax for an anonymous function, so you are still in local scope inside do blocks

1 Like

I don’t get it. Do I have to annotate npoints somehow to make it visible inside an anonymous function, in case there is no let block?

Yes if there is no let block you have to put global npoints somewhere, or place npoints inside some sort of mutable struct or Ref.

But why not just keep the let block? Or put your code into a function?

1 Like

When I use:

  global npoints = 10

I get the same error.

That is what I started with, but then I run into benchmarking issues: Unintuitive behaviour with BenchmarkTools

EDIT

I misunderstood your suggestion. Putting global npoints inside the do block solves the problem.

The let and do here are red herrings. Here are some simpler questions to nudge you in the right direction.

Let’s say I have:

function foo()
  x = ...
end

This unambiguously creates a local variable named x inside foo(), right?

Now let’s say I have:

function foo()
  println(x)
end

This unambiguously refers to some (hopefully existing) global variable named x, right?

Now what if I have:

function foo()
  x = x + 1
end

What is x? If we follow the first example, it’s a local variable, but then the right-hand side makes no sense since that local variable doesn’t exist yet. If we follow the second example, then it’s a global variable, but now we are silently modifying a global variable, which the language does not allow. If we follow both examples, somehow x is both a local and a global variable at the same time, which is even worse. That’s why this situation is an error in Julia, since there’s no obviously right thing to do.

If you want x to refer to the global variable, then you need:

function foo()
  global x
  x = x + 1
end

That works with no issues. If you want x to be a local variable, then you can’t also have it refer to the global variable with the same name.

A let block creates a new local scope, so you can do:

julia> let 
         a = 1 
         function foo()
           a = a + 1  # updates `a` in the parent local scope. 
         end
         foo()
         println(a)
       end
2

as explained in Scope of Variables · The Julia Language

6 Likes

Thank you. This issue crossed my mind the first time I wrote

npoints = max(2, npoints-1)

but it magically worked (I had let block), so I forgot about it. The special treatment of global scope takes time to get used to.

1 Like