Either the def of `local` is wrong, Or the current julia behavior is unexpected

The def:

help?> local
  local introduces a _new_ local variable.

Okay. I think this make sense somehow since

julia> function f(a)
           local a
       end
ERROR: syntax: local variable name "a" conflicts with an argument

In the above example, firstly we have an local argument named a in the first line.
Then in the second line we introduce a new local name, also named a.
So we will have two different local a’s, which will cause ambiguity—thus the ERROR is proper.

Now look at this example

julia> function c()
           local a
           local a
       end
c (generic function with 1 method)

Notice that c can be defined!
I think this doesn’t make sense anyway. It violates the definition of local, isn’t it?

Edit: (Also see my #13 post)

You’re implying that each local a statement must create a new local variable. That isn’t true; the 2nd statement is just redundant for that local scope level. The reason why you get an error in function f(a) instead of it being redundant is because it strongly implies the user forgot about the argument by mistake. Note that local statements often coincide with assignments local a = 0, which would make the argument rather useless. That particular danger doesn’t happen with 2 local a statements, though that likely mistake could have lesser hazards.

1 Like

It would be fine for the language to throw an error for the second example, IMO, but there’s no real ambiguity here. It’s just the same local variable being declared twice.

Now, maybe a function argument and a local variable are two distinct things, arguably. On the other hand, I don’t think I’d be upset if the first example didn’t throw an error, either.

Both of these would probably best be flagged by a linter.

Note that you can do

function f(a)
    @show a
    let  # hard sub-scope
        local a = 2
        @show a
    end
end

It’s not me implying, it’s written as its definition, which is very unclear so that I cannot make further reasoning. You cannot read a math book to the second page with its definitions being wrong.

No I strongly disagree.
The standard syntax is just local x, y, z without involving any =.
Otherwise it’s extremely easy for users to write local x = t.a, y = 4, which mixed up with the let syntax. Several days before I spend 3 hours debugging, and finally discover this stupid bug.

1 Like

The definition could be construed as a little ambiguous. But, now you know that’s not what that means. If you have any ideas on how to formulate that with less ambiguity, you could open a PR for the docstring.

But seriously: does this cause any problems whatsoever with how you reason about Julia code?

What exactly is the bug?

1 Like

For consistency, perhaps the first example should not throw an error since you can do

julia> function f(a)
       a = 1
       end
f (generic function with 1 method)

julia> f(3)
1

so, within the scope of the function, you can certainly overwrite the function argument. There is not really a big difference in writing a = 1 and local a = 1 above, since the scoping is the same. This is also why

function b()
   local a 
   local a
end

is perfectly fine, as nothing prevents you from using the same binding again.

I believe the first error might just be a “we think you are introducing a bug so will error out”.

1 Like

Nothing in the docstring contradicts what I said, and the wider Scope of Variables page in the Manual makes it pretty clear that

  1. local statements introduce a local variable to a particular scope level in contrast with outer local variables of the same name.
  2. There is no such thing as 2 local variables of the same name being accessible within the same local scope level. One name means one variable.

Your personal disagreement and difficulties don’t contradict the plain observation that local statements do coincide with assignments in practice and in the documentation. As for that particular bug, local x = t.a, y = 4 doesn’t work because it violates the syntax of an assignment expression. let header syntax is specified as a series of assignments or declarations with somewhat different parsing, not a single assignment expression like that.

The bug being that this is just invalid syntax, but the compiler doesn’t catch it until runtime and then produces a very confusing error? That does seem like something to open an issue with JuliaSyntax over.

I worded that poorly, I wasn’t trying to say it was invalid syntax. I ought to have said the syntax doesn’t mean a sequence of single-variable assignments as WalterMadelim expected from let headers. Under normal circumstances, it is a chained assignment, one of the left-hand expressions doing destructuring assignment:

julia> mutable struct Blah a end

julia> t = Blah(0)
Blah(0)

julia> x = t.a, y = 1:2 # change because integer only has index 1
1:2

julia> x, t, y
(1:2, Blah(1), 2)

let headers specifying a sequence of assignments or declarations does skew the parsing of otherwise identical text. Here’s an intended destructuring assignment example:

julia> (x, y) = 1:2; x, y
(1, 2)

julia> x, y = 1:2; x, y # we're allowed to omit parentheses here
(1, 2)

julia> let (x, y) = 1:2
        x, y
       end
(1, 2)

julia> let x, y = 1:2 # parser doesn't play nice anymore
        x, y
       end
ERROR: UndefVarError: `x` not defined in local scope

In the last erroring example, we don’t even need the assignments in the let header, and it’d work fine if we move those into the let body. However, the header necessarily exists to pass values from the outer scope into the let body the same way a function call passes keyword arguments into a method body:

julia> a = 1;

julia> let a = a # left assigns local a, right accesses value of global a
         a+10
       end
11

julia> f(;a) = a+10; f(a = a) # same deal here
11

julia> let
         a = a # can't use 1 name as 2 variables
         a+10
       end
ERROR: UndefVarError: `a` not defined in local scope

If it were up to me, I’d further restrict let headers to simpler assignment expressions to mimick keyword arguments and explicitly describe it that way, but that’d be a breaking change.

Yes, seriously.

Until here I still don’t know what is the essential effect by writing local x. Because of people’s inaccurate wording.

I don’t know if local x means:

  • introduce a new name x, which is required to be a local name in the current scope

or

  • We might have owned a name x in the current scope already, and now we declare it to be local.

Your wording is still confusing.

You can look up an English dictionary to find the meaning of introduce:

to mention something for the first time in a piece of writing

The first time here is essentially the same as

Issue created

1 Like

Again, none of that contradicts what I said from the start about multiple local statements in the same local scope level being redundant. They (plural) really do introduce one new local variable to that scope. Nothing in the documentation shares your assertion that each individual statement must contribute a different local variable. Maybe it should assert the opposite, since your misinterpretation is fairly reasonable.

I re-state my point:
either the docstring definition is not correct, or the current julia behavior is wrong. (Now I believe that it is the former case).

e.g.

function unveil()
    let
        local iPhone_16
        iPhone_16 = 1
        local iPhone_16
        iPhone_16 = 2
    end
end

In the let block above, there is a name called iPhone_16.

Do all of us believe that there should be and must be only one entity which is referred to by the name iPhone_16, within the let block?

If all of us believe so, then the docstring definition is wrong!
because we already see iPhone_16, which is associating to 1 in the 4th line.

You cannot “introduce” iPhone_16 in the 5th line because it is not new!
You can introduce iPhone_17 in that place though, which is indeed new.

Just occurred to me to ask, do you think declarations are executed in order, like the assignments are?

Suppose the parser reads my function unveil() above, and it discovers that there is one redundant local iPhone_16, okay suppose the parser removes one local iPhone_16.

Okay now it is fine.

But, Now review my first instance

A fundamental question is:
is the a in the “f(a)” and the a in the “local a” the same name?

If local is just declaring, not introducing, then why cannot the parser remove the local a???

I think these is no ambiguity here:

function simple(a)
    a
end

In the simple function, there is one and only one entity which is referred to by the local name a.
Given this fact, I think the declaration is redundant (because the a being an input arg, is already local). Anyone disagree?
Following this logic, Why should the current julia throw an ERROR??

It demonstrably doesn’t do that, so not sure what to do with your reasoning following a false premise.

julia> Meta.parse("""
       let
       local a
       local a
       end
       """)
:(let
      #= none:2 =#
      local a
      #= none:3 =#
      local a
  end)

I think @WalterMadelim was just a bit imprecise in his wording, and meant that some stage between “read source code” and “execution” has the effect of removing that “double declaration”. Possibly lowering, which sometimes is done in one step with parsing?

3 Likes

I think so

julia> Meta.@lower let
        local a
        a = 1
        local a
       end
:($(Expr(:thunk, CodeInfo(
    @ REPL[100]:3 within `top-level scope`
1 ─     a = 1
└──     return nothing
))))

julia> Meta.@lower let
        local a
        a = 1
        global a
       end
:($(Expr(:error, "variable \"a\" declared both local and global")))

I suspect that there is something bad associated to overwriting/reusing a function arg.
cf. Performance Tips · The Julia Language
The abmult is doing this bad thing.
But abmult2 changed a name r0 stealthily. (I fail to see its motivation in the doc)

This is because the r::Int = r0 declaration is necessary to introduce the type assertion on r. Contrary to popular believe, ::Int in method signatures are NOT type assertions, they are (almost exclusively) relevant for selecting methods during dispatch. It is indeed confusing that they use the same syntax for different things.

1 Like