# \`let\` syntax weirdness

**URL:** <https://discourse.julialang.org/t/let-syntax-weirdness/52738>\
**Category:** Internals & Design\
**Created:** [January 2, 2021, 3:53pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738 "2021-01-02T15:53:23Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 3:53pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/1 "2021-01-02T15:53:23Z")

</div>

`let` is great and very important to have, but the syntax is very unusual.

Before running them, try to guess what these do:

```julia

function f1()
    a = 1
    b = 2
    c = let a = 3
        b = 4
        a+b
    end
    (a,b,c)
end

function f2()
    a = 1
    b = 2
    c = let 
        a = 3
        b = 4
        a+b
    end
    (a,b,c)
end

function f3()
    a = 1
    b = 2
    c = let begin
            a = 3
            b = 4
        end
        a+b
    end
    (a,b,c)
end

```

`f1` is the standard usage of `let`. But the only new binding is for `a`! This is very surprising for anyone coming from languages with `let...in` syntax. There, we’d expect the value of the expression to be the last line in the block. This is not the case.

`f2` looks like it ought to be equivalent, but it’s not! This is one of the very few cases where Julia is whitespace-sensitve.

`f3` seems like it would be a sensible way around this limitation, but it doesn’t even parse!

As a result of this syntax, there are lots of examples in the wild of people using `let` where it doesn’t do anything `let`-like.

Why is `let` designed this way? Can it change for Julia 2.0?

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [January 2, 2021, 4:22pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/2 "2021-01-02T16:22:05Z")

</div>

This is intended and I believe is the normal behavior of `let` from lisp: the let block introduces a new scope, but only the let “arguments” introduce new bindings. Describing this as white space sensitive is a bit misleading: new lines are often very impactful to meaning. I agree it’s a bit confusing though.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 4:30pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/3 "2021-01-02T16:30:29Z")

</div>

Thanks @StefanKarpinski. Maybe part of my confusion is from coming from Ocaml and Haskell. But I think my previous misunderstanding may be very common, and the use case is an important one.

If you need lots of bindings before the return value, do you just have one giant line? Why can’t `begin..end` work in this context?

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 4:35pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/4 "2021-01-02T16:35:26Z")

</div>

Maybe most importantly… If we have a way to introduce a block of bindings, it’s easy to reduce this to just one. But the reverse is very awkward. Worst of all, the current `let` is very often misused, which may lead to lots of subtle bugs in the ecosystem.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 4:43pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/5 "2021-01-02T16:43:48Z")

</div>

For example, here’s some code from @shashi and @Mason, both of whom I consider to be top-tier developers:

> <https://github.com/JuliaSymbolics/SymbolicUtils.jl/blob/master/src/simplify_rules.jl>

No shame here, the point is that if it’s throwing them, it’s throwing _many_ others.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [January 2, 2021, 4:56pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/6 "2021-01-02T16:56:21Z")

</div>

While I think that’s a very weird use of a `let` block, I’m not sure it’s showing a misunderstanding. Could you elaborate on what you see as wrong with it?

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 5:06pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/7 "2021-01-02T17:06:20Z")

</div>

Maybe I’m misunderstanding how you’re using it, in which case I owe you an apology.

When I see someone use `let`, I assume they intend to introduce local bindings. As I now understand `let`, it’s only the `let` line itself that’s special, otherwise it just acts like a `begin`. So as it’s used here, it seems to make no difference at all and could just be removed entirely.

I’d guess this is the case for the vast majority of uses out there. We usually put things in blocks with newline separations, but there’s this one weird exception and no clean way around it that I can see.

Or… maybe I’m wrong? That’s fine too, I’d just like to understand if that’s the case.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [January 2, 2021, 5:15pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/8 "2021-01-02T17:15:19Z")

</div>

If I may add an additional point of confusion to me, is that the bindings behave differently if inside a function or in the global scope:

```julia
julia> function f1()
         a = 1
         b = 2
         c = let a = 3 
           b = 4
           a+b
         end
         (a,b,c)
       end
f1 (generic function with 1 method)

julia> f1()
(1, 4, 7)

julia> a = 1
       b = 2
       c = let a = 3
         b = 4
         a+b
       end
       (a,b,c)
(1, 2, 7)

julia>

```

In one case `b` is local to the function, in the other case it is local to the let block only.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [January 2, 2021, 5:15pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/9 "2021-01-02T17:15:39Z")

</div>

No, `let` introduces a local scope through the whole block. The first line has different special properties though. What you observed above was that nesting local scopes does’t separate them unless you take advantage of the first line of the `let` block to tell julia you’re rebinding variables.

Perhaps this is helpful:

```julia
julia> let
           a = 1
       end;

julia> a
ERROR: UndefVarError: a not defined

julia> begin
           a = 1
       end;

julia> a
1

julia> let
           let
               a = 1
           end
           a
       end
1

```

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [January 2, 2021, 5:17pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/10 "2021-01-02T17:17:11Z")

</div>

I have to say that I was able to guess correctly the results of `f1` and `f2`. Only for `f3` I had no idea about what would happen, and this seem to not be a problem, as this case is a parsing error.

> [@cscherrer](#):
>
> As I now understand `let` , it’s only the `let` line itself that’s special, otherwise it just acts like a `begin` . So as it’s used here, it seems to make no difference at all and could just be removed entirely.

This is wrong. `let` introduces a local scope. `begin ... end` does not introduce a new local scope. Therefore, `let` is a useful tool to take out things of global scope without putting them inside a function.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [January 2, 2021, 5:21pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/11 "2021-01-02T17:21:30Z")

</div>

In your example, the source of the difference is not the `let` itself, but the difference between `global` and non-`global` scope, you could be using a `for` or any other block that introduces local scope.

For your second example to be compatible with the first it should be written as:

```julia
julia> a = 1
       b = 2
       c = let a = 3
         global b = 4
         a+b
       end
       (a,b,c)
(1, 4, 7)

```

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [January 2, 2021, 5:25pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/12 "2021-01-02T17:25:39Z")

</div>

> [@Henrique\_Becker](#):
>
> you could be using a `for` or any other block

Well, that difference was removed in 1.5 for `for` loops (of course you know that already):

```julia
julia> a = 1
       b = 2
       for i in 1:1
         b = 4
       end
       (a,b)
(1, 4)

julia>

```

So the new scope introduced by `let` behaves differently than the new scope of `for` when used from the global scope now.

Just to be clear: I do not think that that should be different by any reason, just pointing out that this might be a source of confusion because a copy/paste of a block of code can result in different behaviors.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [January 2, 2021, 5:58pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/13 "2021-01-02T17:58:34Z")

</div>

Yes, _and no_. I had forgot about this distinction (at the moment I am locked at 1.4.2). However, even in Julia 1.5+, this distinction only happens in the REPL. What I said is the correct behavior if your are executing the code from a script. We did not consider this distinction until now (if the code executes from the REPL or a script), so I would say that both our comments are correct from the right perspective and wrong in general. The relevant section of the manual is [this one](https://docs.julialang.org/en/v1/manual/variables-and-scoping/#Local-Scope).

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 5:58pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/14 "2021-01-02T17:58:44Z")

</div>

> [@Mason](#):
>
> nesting local scopes does’t separate them unless you take advantage of the first line of the `let` block to tell julia you’re rebinding variables.

Well this just get weirder and weirder. Especially since you get

> [@Mason](#):
>
> ```julia
> julia> let
> let
> a = 1
> end
> a
> end
> 1
> 
> ```

But I get (Julia 1.5)

```julia
julia> let
           let
               a = 1
           end
           a
       end
ERROR: UndefVarError: a not defined
Stacktrace:
 [1] top-level scope at REPL[2]:5
 [2] run_repl(::REPL.AbstractREPL, ::Any) at /build/julia/src/julia-1.5.3/usr/share/julia/stdlib/v1.5/REPL/src/REPL.jl:288

```

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [January 2, 2021, 6:00pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/15 "2021-01-02T18:00:59Z")

</div>

I don’t see it as that weird. It’s using the fact that `let` creates a local scope to hide a bunch of variables that are closed over. It is not using the ability of `let` to shadow an existing local binding with a new local binding. In this case there’s little difference since the outer scope is global, there are no outer bindings with those names, and the let block isn’t in a loop or any other structure where creating a new binding on each evaluation would be observable.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [January 2, 2021, 6:02pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/16 "2021-01-02T18:02:52Z")

</div>

I really do not remember this example from @Mason ever being valid. I think that maybe there was an `a` variable in the scope outside the outer `let` that was forgotten.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 6:03pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/17 "2021-01-02T18:03:04Z")

</div>

In any case I owe @Mason and @shashi an apology. I do think there’s some weirdness here, but a lot of this seems to be my misunderstanding, and I should have sorted that out before dragging them into it. Sorry. Also I’m very much enjoying SymbolicUtils.jl, and I’m excited to see where it goes from here 🙂

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [January 2, 2021, 6:07pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/18 "2021-01-02T18:07:11Z")

</div>

> [@Henrique\_Becker](#):
>
> `a` variable in the scope outside the outer `let` that was forgotten.

Effectively.

```julia
julia> a = 2
2

julia> let
         let
           a = 1
         end
         a
       end
2

```

Yet one could perhaps expect that let block to result in undefined `a` anyway, since its scope is local (but since we are not modifying `a` it is considered as the `global a`). One has to get used to this.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 6:11pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/19 "2021-01-02T18:11:43Z")

</div>

> [@Henrique\_Becker](#):
>
> This is wrong. `let` introduces a local scope. `begin ... end` does not introduce a new local scope. Therefore, `let` is a useful tool to take out things of global scope without putting them inside a function.

Right, I think some of my confusion is coming from the quirks of local scope in Julia. Is it correct to say that the first line of a `let` has hard local scope, and the rest has soft local scope?

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [January 2, 2021, 6:24pm UTC](https://discourse.julialang.org/t/let-syntax-weirdness/52738/21 "2021-01-02T18:24:59Z")

</div>

Ok, so the cleanest way I can see to get the behavior I was expecting is

```julia
julia> function f4()
           a = 1
           b = 2
           c = let
               local a = 3
               local b = 4
               a+b
           end
           (a,b,c)
       end
f4 (generic function with 1 method)

julia> f4()
(1, 2, 7)

```

[Next page](https://discourse.julialang.org/t/let-syntax-weirdness/52738.md?page=2)
