Using Let block

If I define a variable b in Main and then use the let block:

b = 5
let 
  b = b
  println(b)
end

I get the error saying: " UndefVarError: b not defined"

I can get this to work by doing:

b = 5
let 
  b = Main.b
  println(b)
end

Is there a way to do this which doesn’t use Main but instead whatever level this was called in? In other words if I did this within a function, then I wouldn’t want to use the word Main.

I tried using the escape function but wasn’t successful.

Thanks,

You can also do:

julia> b = 5
5

julia> let 
         global b
         println(b)
       end
1 Like

No that doesn’t do anything. If you want a new b inside the let block, put it on the first line of the let block

julia> let b = b
           println(b)
       end
5
8 Likes

Wow. Very interesting. I needed to do multiple variables, but I just created a block in first line and it works. Thanks!

When defining multiple variables in a let block, I tend to favor the following syntax (which I learned about here on Discourse, too). In the line of the let keyword, I only list the variables to be given a local scope, without their assigned values. And then, in the first fiew lines of the body of the let block, I assign their values. E.g.,

global_value = let x, y
    x = <expression-to-compute-x>
    y = <expression-to-compute-y>
    <expression-using-x-and-y>
end
2 Likes

Yes, but only in the first line of the let block do you have the ability to create a new binding for an existing variable, while still referring to that variable for the assigned value. (And this was precisely the use case mentioned in the OP).

Consider the following example:

julia> x = 1
1

# in the lhs, x refers to a new binding
# in the rhs, x refers to the global variable
julia> let x = x + 1
           x *= 2
       end
4

julia> x
1

but

julia> x = 1
1

julia> let x         # <- new binding introduced here, shadows the global variable everywhere in the let body
           x = x + 1 # x refers to the new variable in both lhs and rhs
           x *= 2    #   => ERROR
       end
ERROR: UndefVarError: `x` not defined
Stacktrace:
 [1] top-level scope
   @ REPL[4]:2
2 Likes

Fair point. If you need to use the values of some shadowed variables, you have no other option than to use them in the first line of the let block. I don’t like rebinding variables, though, so this is usually not an issue for me.

In this case, is it possible to omit the variables on the let line?

global_value = let
    x = <expression-to-compute-x>
    y = <expression-to-compute-y>
    <expression-using-x-and-y>
end

That will reassign existing variables. If you want new variables they have to go in the first line of the let.

Edit: it’s different if the existing variables are globals. See below.

2 Likes

No I don’t think so:

julia> x=5
5

julia> let
           x=10
       end
10

julia> x
5

Because let creates a local scope and assignments in local scopes create a new binding. If you only used x inside the let block then you’d get the global value. But in any case you cannot overwrite it globally without using global x somewhere.

I think your example does rely only on the global. Assignments in the body of a let otherwise reassign existing variables from enclosing scopes.

julia> function f()
           x = 5
           let
               x = 10
           end
           x
       end
f (generic function with 1 method)

julia> f()
10
1 Like

Your are right of course. Here x is already a local binding - just not local to the let-block (which I honestly find a bit confusing). Arguably that is the default mode of operation for let and what I posted a special case for true global variables.

I think, I just interpreted the @greg_plowman’s code to be in global scope due to the variable named global_value.

Good that you cleared this up :slight_smile:

1 Like

Can I ask, what is the reason for wanting to do this?

You can write your code like this:

b = 5
another_b = b
println(b)

There is no let block there. That suggests to me I haven’t understood why you wanted to introduce a new scope?

My package Handcalcs.jl can find functions and “unpack” the lines in the function into the current scope. See here. But I don’t actually want to introduce those variables into the current scope, so I wrap them in a let block.

lets say I have a function:

function my_add(a, b)
c = a + b
end

If I write something like:

a = 2
b = 3
@handcalcs c = my_add(a, b)

The handcalcs macro will turn this into something like:

c = let (a, b) = (a, b)
c = a + b
end

There is more going on in the macro but this was the intent of the question. When the parameter names happen to be variables names in the current scope, the let block will not see the variables in the current scope so it needs to be in the first line rather than the second part of the let block.

Right I see, so it was to protect users of your macro from clobbering their locals?

Yes. Exactly

1 Like