VS Code error: Varaible has been assigned but not used

Hello

I am puzzled by an error message in VS code. I have the folloing function below and VS Code is telling me that at line 11, where I compute the likelihood, that VARIABLE HAS BEEN ASSIGNED BUT NOT USED.

The function seems to work fine! It is NOT returning 0.0, it is returning the correct value.

function dummyfunc(tile::UInt32, W::Vector{BigFloat})::BigFloat
    likelihood = BigFloat(0.0)
    T =  T3x3(W)
    a = pairs(patchdictionary)
    foreach(a) do paire
        if tile == paire[1]
            p1 = Int(paire[1] & 0x000000ff)
            p2 = Int((paire[1] & 0x0000ff00) >> 8)
            p3 = Int((paire[1] & 0x00ff0000) >> 16)
            p4 = Int((paire[1] & 0xff000000) >> 24)
            likelihood = (W[p1] * W[p2] * W[p3] * W[p4]) / T # <--- This is where 'likelihood' is greyed out by VS Code
        end
    end
    return likelihood
end

It seem a bug in the linter for me. Yet, isn´t this clearer?

function dummyfunc(tile::UInt32, W::Vector{BigFloat})::BigFloat
    likelihood = BigFloat(0.0)
    T =  T3x3(W)
    for paire in pairs(patchdictionary)
        if tile == paire[1]
            p1 = Int(paire[1] & 0x000000ff)
            p2 = Int((paire[1] & 0x0000ff00) >> 8)
            p3 = Int((paire[1] & 0x00ff0000) >> 16)
            p4 = Int((paire[1] & 0xff000000) >> 24)
            likelihood = (W[p1] * W[p2] * W[p3] * W[p4]) / T # <--- This is where 'likelihood' is greyed out by VS Code
        end
    end
    return likelihood
end
help?> foreach
search: foreach

  foreach(f, c...) -> Nothing

  Call function f on each element of iterable c. For multiple iterable arguments, f is called elementwise, and
  iteration stops when any iterator is finished.

  foreach should be used instead of map when the results of f are not needed, for example in foreach(println, array).

Therefor it seems to be the right thing to say it’s not used.
Anyways, as we are in a function, the scope of likelihood seems not to change, so the value of likelihood is changed inside of the anonymous function, e.g. a MWE (VSCode shows the same behaviour):

julia> function foo()
           a=[1,2,3]
           y=0
           foreach(a) do x
               y=x
           end
           y
       end
foo (generic function with 1 method)

julia> foo()
3

https://docs.julialang.org/en/v1.9/manual/variables-and-scoping/#scope-of-variables
says the scope of do is local(hard) which means:

  1. Hard scope: If x is not already a local variable and assignment occurs inside of any hard scope construct (i.e. within a let block, function or macro body, comprehension, or generator), a new local named x is created in the scope of the assignment;

and the result of above MWE seem to contradict this. (Julia 1.9 here)

So, VSCode is right but scope rules seem to be not fulfilled or misinterpreted by me.

1 Like

Scope experts needed here!

I think removing the do syntax always makes things clearer:

julia> function foo()
           x = 1
           y = [1,2]
           foreach(el -> x += 1, y)
           return x
       end
foo (generic function with 1 method)

julia> foo()
3

the anonymous function el -> x += 1 is closing over the x value. In general it is not a good idea to modify closed over variables, but that what’s going on there. I think it is a linker bug, there.

1 Like

I think this example is a bit misleading.
It would better be written as:

function foo2()
    a=[1,2,3]
    y=0
    foreach( x-> y=x, a)
    y
end

Or even more explicit:

julia> function foo3()
           a=[1,2,3]
           y=0
           function f(x)
               y=x
           end
           f(3)
           y
       end
foo3 (generic function with 1 method)

julia> foo3()
3

So, the remaining question is:
Why does function f(x) inside foo3 not introduce a new local scope with a local y ?
Instead it inherits the scope of foo3.

Or: Why is local needed here:

julia> function foo4()
           a=[1,2,3]
           y=0
           function f(x)
               local y=x
           end
           f(3)
           y
       end
foo4 (generic function with 1 method)

julia> foo4()
0

I can’t find a good explanation from the docs of scope.

It seems to be clear here:

"When x = <value> occurs in a local scope, Julia applies the following rules to decide what the expression means based on where the assignment expression occurs and what x already refers to at that location:

  1. Existing local: If x is already a local variable, then the existing local x is assigned;"

The “local scope” rules are very comprehensively explained here: Scope of Variables · The Julia Language

(though I admit one learns that by doing mostly, than by reading the docs)

The docs excerpt you quoted above does explain what’s going on here – likelihood is an already present local variable, after all.

Regarding the original issue: It’s a bug in the linter, yes. The scope analysis done there is by no means perfect.

1 Like

Found the right part:

  1. Inner function scopes are just like any other nested local scope. In particular, if a variable is already a local outside of an inner function and you assign to it in the inner function, the outer local variable is updated.

Thanks very much @oheil, I have never thought the issue was with foreach

I simple substitution by for paire in a instead of foreach has solved the problem.

1 Like

One more reason to avoid nested functions when possible.

Note one additional danger in this case: I don’t see any guarantee concerning the order of evaluation of foreach, meaning that in principle the result of such a calculation can change, and there could be even data races.

1 Like