为什么 Julia 中的循环会引入自己的作用域

Sir*_*fus 7 julia

作为 Julia 的新手,我和其他许多人一样,对 Julia 中的循环创建自己的本地作用域(但不在 REPL 上或在函数内)这一事实感到困惑。网上有很多关于这个主题的讨论,但这里的大多数问题都是关于这种行为的细节,例如为什么在循环内执行 a=1 不会影响循环外的变量 a,但 a[1]=1作品。我现在大部分情况都知道它是如何工作的了。

我的问题是为什么 Julia 会采用这种行为。从用户的角度来看,这有好处吗?我想不出一个。或者出于某种技术原因有必要吗?

如果已经有人问过这个问题,我深表歉意,但到目前为止我看到的所有问题和答案都是关于它是如何工作的以及如何处理它,但我很好奇为什么 Julia 是这样实现的。

cbk*_*cbk 4

首先,如果循环外部的作用域是全局作用域,那么 Julia 中的循环只会引入一个新的作用域,该作用域会隐藏循环外部存在的变量(根据您的抱怨)。所以,例如

function foo()
    a = 0
    # Loop does not hide existing variable `a`, will work just fine
    for i = 100
        a += i^2
    end
    return a
end
Run Code Online (Sandbox Code Playgroud)
julia> foo()
10000
Run Code Online (Sandbox Code Playgroud)

换句话说

# Anywhere other than global scope
a = 0
for i = 100
    a += i^2
end
a == 10000 # TRUE
Run Code Online (Sandbox Code Playgroud)

这是因为在 Julia 中,与许多其他语言一样,全局范围可能被认为是有害的。至少,for具有全局范围的循环会遇到严重的性能损失。例如,考虑以下情况:

julia> a = 0
0

julia> @time for i=1:100
          # Technically this "global" keyword is superfluous since we're running this at the repl, but doesn't hurt to be explicit
          global a += rand()^2
       end
  0.000022 seconds (200 allocations: 3.125 KiB)

julia> function bar()
           a = 0
           for i=1:100
               a += rand()^2
           end
           return a
       end
bar (generic function with 1 method)

julia> @time bar()
  0.000002 seconds
33.21364180865362
Run Code Online (Sandbox Code Playgroud)

请注意分配的巨大差异(底部版本为零)和大约 10 倍的时间差异。

现在,您可能已经注意到我global在全局示例中使用了一个特殊关键字,但由于它是在 REPL 中运行的,因此除了明确正在发生的情况之外,它实际上没有做任何事情。

这给我们带来了您注意到的另一个显着差异:在 REPL 中运行时,for循环似乎不会引入新的作用域,即使 REPL 肯定是全局作用域。这是因为,在调试时,必须向global从程序深处的某个位置(例如在函数内,循环隐藏外部变量)复制粘贴的代码添加一堆限定符是一件非常痛苦的事情。因此,为了调试时的方便,REPL 有效地为您添加了这些global关键字,并假设如果您关心性能,您不会只是将原始循环粘贴到 REPL 中,并且如果您只是将原始循环粘贴到REPL,你可能正在调试什么的。

然而,在脚本中,假定您确实关心性能,因此如果您尝试在循环中使用全局变量而不明确声明它,您将收到错误。

正如其他答案以技术上更正确的术语解释的那样,细节要复杂得多。据我所知,其中一些复杂性是由于在 Julia v0.7 左右发生的 REPL 循环中默认情况下全局变量是否应该可访问的决定发生了逆转。