Why does this assignment inside a loop fail in Jul

2019-05-21 05:18发布

(k, a, b, a1, b1) = (BigInt(2), BigInt(4), BigInt(1), BigInt(12), BigInt(4))

while k <= BigInt(4)
  (p, q, k) = (k*k, BigInt(2)*k+BigInt(1), k+BigInt(1))
end

This code compiles and runs in Julia 0.6, but in 1.0 produces ERROR: UndefVarError: k not defined.

Did something change between versions? What's wrong with this code in Julia 1.0?

标签: julia
2条回答
老娘就宠你
2楼-- · 2019-05-21 05:23

Yes, something changed. I think this is what's going on:

Assigning to p, q, and k inside the loop does actually creates new p, q, and k bindings in a new local scope. Then, because k is also used on the right-hand side, Julia tries to use the local k and fails.

In order to obtain the expected semantics (mutating the existing p, q, and k defined at the top level), you need to declare them "global" first:

while k <= BigInt(4)
  global p, q, k

  (p, q, k) = (k*k, BigInt(2)*k+BigInt(1), k+BigInt(1))
end

edit: thanks to user AnAverageHuman on Freenode for figuring this out.

查看更多
地球回转人心会变
3楼-- · 2019-05-21 05:47

The answer by shadowtalker is correct. However, there is one important issue with this code that is worth adding some more explanation (and it was too long for a comment).

The relevant scoping rules in Julia for this case are the following (and if you want to read the details you can find them here https://docs.julialang.org/en/latest/manual/variables-and-scoping/#Local-Scope-1):

  • while block introduces a new local scope;
  • local scope inherits variables from enclosing global scope only for reading (unless they are qualified with global keyword as explained by shadowtalker);
  • local scope inherits variables from enclosing local scope for reading and writing unless they are qualified with local keyword.

Now - why this is important? The reason is that your code will behave differently if you run it in global scope (e.g. in Julia REPL) and differently if you have it in a local scope (e.g. inside a function).

If you run it in global scope then what shadowtalker showed is needed. However, if yourun it e.g. inside a function you do not have to change anything. For example this function will work correctly:

function f()
    (k, a, b, a1, b1) = (BigInt(2), BigInt(4), BigInt(1), BigInt(12), BigInt(4))

    while k <= BigInt(4)
      (p, q, k) = (k*k, BigInt(2)*k+BigInt(1), k+BigInt(1))
    end
end

also without creating a function if you e.g. use let block in a global scope the code will just run as intended:

let
    k, a, b, a1, b1 = BigInt(2), BigInt(4), BigInt(1), BigInt(12), BigInt(4)

    while k <= BigInt(4)
      (p, q, k) = (k*k, BigInt(2)*k+BigInt(1), k+BigInt(1))
    end

    k, a, b, a1, b1
end

because let creates a local scope (I have added the statement at the end of let block to make it evaluate to the values of the introduced variables so that you can inspect them).

This is a significant change from Julia 0.6 and earlier where Julia made a difference between hard and soft local scopes (see here https://docs.julialang.org/en/release-0.6/manual/variables-and-scoping/#scope-of-variables-1), but this distinction is gone - all local scopes now behave identically. This is a significant change an in practice it means that you can expect that the code that runs correctly inside some local scope (a function in most cases) will change its behavior if you copy-paste it to a global scope. From my experience using let blocks as shown above is the simplest way alleviate this problem.

查看更多
登录 后发表回答