# Why does \`let\` allow comma-separated assignments?

**URL:** <https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812>\
**Category:** General Usage\
**Tags:** question\
**Created:** [May 3, 2024, 6:43pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812 "2024-05-03T18:43:45Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [May 3, 2024, 6:43pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/1 "2024-05-03T18:43:45Z")

</div>

This is something I’ve always wondered… Why does a `let` block allow comma-separated assignments?

```julia
let x = 1, y = 2, z = 3
    (x, y, z)
end

```

As far as I can tell, this is completely identical to

```julia
let x = 1; y = 2; z = 3
    (x, y, z)
end

```

or

```julia
let
    x = 1
    y = 2
    z = 3
    (x, y, z)
end

```

This also doesn’t seem (?) to change the scoping rules:

```julia
a = 1
let x = a
    x
end

# equivalent to
let
    x = a
    x
end

```

Furthermore, `let` evaluates the expressions in order, so you can do

```julia
let x = 1, y = x + 1, z = y + 1
    (x, y, z)
end

```

which is actually the same as if you had `;`-separated expressions. So… why not just use `;` here, rather than using the alternate behavior of `,`?

I find this a bit strange because in a `begin` block, or simply typed to the REPL, the expression `x = 1, y = 2, z = 3` is invalid. It’s also weird because `(x = 1, y = 2, z = 3)` normally instantiates a named tuple, rather than assigning variables (or for defining default arguments of a function).

Is this some vestigial syntax from earlier Julia versions? Or perhaps I am missing something subtle about the scoping rules here?

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [May 3, 2024, 6:51pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/2 "2024-05-03T18:51:45Z")

</div>

they are not quite identical. consider

```julia
julia> let
           x = 0
           let
               x = 1
           end
           x
       end
1

julia> let
           x = 0
           let x = 1 end
           x
       end
0

```

assignments within a `let`-block follow normal local-variable scoping and assignment rules

assignments on the same line as `let` and comma-separated follow the same scoping rules as function argument binding, so `let x=1; do_stuff; end` is akin to `(x->do_stuff)(1)`

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [May 3, 2024, 6:55pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/3 "2024-05-03T18:55:10Z")

</div>

Very weird… because

```julia
julia> x = 0
0

julia> let
           x = 1
       end
1

julia> x
0

```

So something about being within the outer `let` block overrides the inner one?

Or even

```julia
julia> begin
           x = 0
           let
               x = 1
           end
           x
       end
0

julia> let
           x = 0
           let
               x = 1
           end
           x
       end
1

```

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [May 3, 2024, 6:55pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/4 "2024-05-03T18:55:18Z")

</div>

I complain about this here

> <https://github.com/JuliaLang/julia/issues/51183>
>
> The \[documentation\](https://docs.julialang.org/en/v1/base/base/#let) says
> 
> \> A…dditionally, the syntax has a special meaning for comma-separated assignments and variable names that may optionally appear on the same line as the let....The variables introduced on this line are local to the let block
> 
> 
> These look similar but do very different things:
> 
> \`\`\`jl
> function foo()
> x = 1
> let x = 2
> x
> end
> x
> end
> foo() == 1
> 
> 
> function bar()
> x = 1
> let
> x = 2
> x
> end
> x
> end
> bar() == 2
> \`\`\`
> 
> Also putting bindings on separate lines for readability isn't possible without changing the meaning.
> 
> I request a change to this syntax to make it easier to 
> 
> \- notice the difference between binding and assignment
> \- put bindings on separate lines for readability without risk of mutating the enclosing scope (currently done with commas, but not very readable if the expressions are long)
> 
> One way to do this is adding a token to separate the place where \`=\` makes bindings from where it makes assignments, such as \`within\`:
> 
> \`\`\`jl
> let x = 1 # binding
> y = 2 # binding
> within 
> x = 2 # assignment
> x + y
> end
> \`\`\`
> 
> 
> This could be a 2.0 change but ideally there is change to address the issues in 1.x, allowing \`within\`.
> 
> 
> \### Other languages
> 
> In Scheme, bindings are separated with parentheses and assignments are made with \`set!\`:
> 
> \`\`\`jl
> 
> (let 
> ((x 1) ;; binding
> (y 5)) ;; binding
> (+ x y))
> \`\`\`
> 
> In OCaml, \`in\` separates the bindings component from the expression that produces the value:
> 
> \`\`\`ocaml
> let n = 2 in n \* n
> \`\`\`
> 
> and likewise in Nix:
> 
> \`\`\`jl
> 
> let 
> x = 1;
> y = 1;
> in
> x + y
> \`\`\`

and somewhat relatedly here

> <https://github.com/JuliaLang/julia/issues/51223>
>
> This is inspired by https://github.com/JuliaLang/julia/issues/51107 and https://…github.com/JuliaLang/julia/issues/51183 where there is ambiguity between defining and reassigning. I still dislike that when writing \`x =\` inside a function I have to worry about whether \`x\` is defined in the enclosing scope.
> 
> I like how Fortress distinguished reassignment with \`:=\` from defining with \`=\` so it couldn't happen by accident (and as a one-character "tax" on reassignment). In Scheme/Racket assignment is \`(set! x 2)\`. 
> 
> Suppose I want to always be explicit about whether I'm defining a new variable or changing the old one and have a linter disallow ambiguous cases. For defining a new variable there is \`local x\`. Could Julia also have a syntax for explicitly reassigning, like \`x := 2\` ?
> 
> Example
> 
> \`\`\`jl
> julia\> function f()
> x = 1
> function g()
> x = 2
> end
> g()
> x
> end
> f (generic function with 1 method)
> 
> julia\> f()
> 2
> \`\`\`
> 
> \--- 
> 
> Update: Pointed out on \[Discourse\](https://discourse.julialang.org/t/on-using-vs-for-assignment/110302), a problem with the proposed syntax is that in Golang the rule is the other way around: \`:=\` is definition and \`=\` is assignment. This could potentially cause confusion for some users. However, only 3% of Julia users use Go (\[2023 Julia survey\](https://julialang.org/assets/2023-julia-user-developer-survey.pdf)) and, sociologically speaking, afaict Go's market doesn't have too much overlap with Julia's. Moreover, SQL and Mathematica, which share far more users with Julia, already use \`=\` for different meanings without problem. Definition syntax is so ubiquitous everybody's gonna understand very quickly what the rule is. So I suspect it’s relatively unlikely to cause excessive confusion in practice. Also, having the slightly shorter \`=\` syntax be for definition rather than reassignment is a nice mild encouragement in the right direction.
> 
> Stefan suggested an opt-in to requiring \`:=\` for reassignment, or using \`\<-\` for reassignment.

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [May 3, 2024, 7:00pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/5 "2024-05-03T19:00:11Z")

</div>

this is because REPL (and the `begin`) block form a global, not local scope. so the second assignment in your `let` block follows the local-vs-global rules, not local-vs-parent-local

if you ran that code from a `.jl` instead of in the REPL, I (think) you would see a warning printed

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [May 3, 2024, 7:12pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/6 "2024-05-03T19:12:42Z")

</div>

Gotcha, thanks. Super weird stuff!

So basically it’s kind of like this? (see annotations)

```julia
julia> begin
           x = 0 # global
           let
               x = 1 # local 1
           end
           x # global
       end
0

julia> let
           x = 0 # local 1
           let
               x = 1 # local 1
           end
           x # local 1
       end
1

```

and then, with the assignments on the same line:

```julia
julia> begin
           x = 0 # global
           let x = 1 # local 1
           end
           x # global
       end
0

julia> let
           x = 0 # local 1
           let x = 1 # local 2
           end
           x # local 1
       end
0

```

Finding it hard to wrap my head around this. Especially the fact that the innermost `let` changes its behavior because it exists within an outer `let`. (example 2 in the first code block)

> [@adienes](#):
>
> if you ran that code from a `.jl` instead of in the REPL, I (think) you would see a warning printed

I don’t see such a warning. (Maybe related to @jar1’s issues.)

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [May 3, 2024, 7:20pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/7 "2024-05-03T19:20:32Z")

</div>

those annotations seem about right. note that `local 2` shadows `local 1` so you lose access to the “surrounding” `x` within the second `let` block. in Python one could use the keyword `nonlocal` to access `local 1` but Julia does not support this (intentionally, I think)

> [@MilesCranmer](#):
>
> I don’t see such a warning.

ah, nvm. that warning is only for soft-scope assignments

---

<div class="post-metadata">

**Author:** ![Barget](https://avatars.discourse-cdn.com/v4/letter/b/94ad74/32.png) [@Barget](https://discourse.julialang.org/u/Barget)\
**Post date:** [May 3, 2024, 9:06pm UTC](https://discourse.julialang.org/t/why-does-let-allow-comma-separated-assignments/113812/8 "2024-05-03T21:06:46Z")

</div>

This interesting (but somewhat cryptic) discussion make me wish for a macro `@scope` that would just print the scope of the variable when the line is evaluated. Something in the spirit of:

```julia
julia>begin
          @scope x = 0
          @scope let x = 1
          end
          x
       end
In line 2: x = 0: global scope of module Main
In line 3: x = 1: local scope (nesting level 1) of function #...

```

Disclamer:  
If there’s such macro, I’m not aware of it.  
I’m not skilled enough to dig that deep into julia’s code and make a PR.
