Julia: 全局变量范围规则导致 REPL/notebook 上的不直观行为

创建于 2018-08-21  ·  98评论  ·  资料来源: JuliaLang/julia

示例 1

这想出了一个直接从 0.6 升级到 1.0 的学生,所以甚至没有机会看到弃用警告,更不用说找到新行为的解释了:

julia> beforefor = true
true

julia> for i in 1:2
         beforefor = false
       end

julia> beforefor  # this is surprising bit
true

julia> beforeif = true
true

julia> if 1 == 1
         beforeif = false
       end
false

julia> beforeif  # Another surprise!
false

julia> function foo()
         infunc = true
         for i in 1:10
           infunc = false
         end
         <strong i="7">@show</strong> infunc
       end
foo (generic function with 1 method)

julia> foo()  # "I don't get this"
infunc = false 

示例 2

julia> total_lines = 0
0

julia> list_of_files = ["a", "b", "c"]
3-element Array{String,1}:
 "a"
 "b"
 "c"

julia> for file in list_of_files
         # fake read file
         lines_in_file = 5
         total_lines += lines_in_file
       end
ERROR: UndefVarError: total_lines not defined
Stacktrace:
 [1] top-level scope at ./REPL[3]:4 [inlined]
 [2] top-level scope at ./none:0

julia> total_lines  # This crushs the students willingness to learn
0

我“明白”为什么会发生这种情况,我认为我可以解释,并充分参考手册中关于什么引入范围和什么不引入的奥秘,但我认为这对于交互式使用是有问题的。

在示例一中,您会遇到无声失败。 在示例 2 中,您会收到一条错误消息,非常有道理。 这与我今天工作时在笔记本中编写的一些 Python 代码大致相当。

我不确定 Python 中的规则是什么,但我知道通常你不能在不调用 global 的情况下在全局范围内分配事物。 但是在 REPL 中它确实有效,大概是因为在 REPL 中,规则不同或相同的逻辑就好像它们都在应用的功能范围内一样。

我无法为规则提供足够的语言律师来提出我想要的具体更改,并且基于 Slack,某些人甚至不一定认为这是一个问题,所以我不知道该去哪里,除了标记它。

交叉引用:

19324

https://discourse.julialang.org/t/repl-and-for-loops-scope-behavior-change/13514
https://stackoverflow.com/questions/51930537/scope-of-variables-in-julia

REPL minor change

最有用的评论

@JeffBezanson ,请记住,在线性代数和统计学等技术课程中,我们中的许多人都希望使用 Julia 来代替 Matlab 等。 这些不是编程课程,学生通常没有编程背景。 我们从不进行结构化编程——它几乎都是与短片断和全局变量交互的。

此外,我首先使用动态语言的原因是在交互式探索和更严格的编程之间流畅地切换。 无法在全局和函数上下文中使用相同的代码是实现这一目标的障碍,即使对于习惯于定义概念范围的人来说也是如此,对于来自非 CS 背景的学生来说,情况更糟。

所有98条评论

(根据@mlubin ,这是相关更改 https://github.com/JuliaLang/julia/pull/19324)

Stefan在这里建议解决这个问题的一种可能性是在let块中自动包装 REPL 条目

但这不会令人困惑,因为你不能做

a = 1

然后使用a ? 除非为所有顶级分配插入global ,我猜?

这种行为不仅仅是将所有内容都包装在let块中——它比这更复杂。 您需要对在表达式中分配的任何全局变量进行 let-bind,然后将 let-bound 值提取到表达式末尾的全局变量中。

所以你可以把a = 1变成类似a = let a; a = 1; end 。 和类似的东西

for i in 1:2
    before = false
end

会变成这样:

before = let before = before
    for i in 1:2
        before = false
    end
end

坦率地说,我对人们现在只提供这种反馈感到非常恼火。 这个变化已经在master上十个月了。

我很内疚直到最近才非常封闭地关注 master,所以这个反馈确实有点晚了。 不仅仅是程序员的问题(大多数for循环将在库代码中的函数内)恐怕这是教学的问题。 通常在函数或作用域之前教授for循环(当然,您需要了解作用域才能真正了解正在发生的事情,但在教学中通常会简化)。

在不解释函数或全局变量的情况下,教初学者如何对 1 到 10 的数字求和变得有点困难。

坦率地说,我对人们现在只提供这种反馈感到非常恼火。 这个变化已经在master上十个月了。

公平地说,Julia 0.7 是在 13 天前发布的。 对于大多数 Julia 用户来说,这是一个新的变化。

坦率地说,我对人们现在只提供这种反馈感到非常恼火。 这个有变化已经在master上十个月了

不幸的是,对于我们这些无法处理边缘生活的人来说,从我们的角度来看,这是全新的。

坦率地说,我对人们现在只提供这种反馈感到非常恼火。 这个变化已经在master上十个月了。

对于我们这些被鼓励远离开发分支的人来说,“从我们的角度来看,这是全新的。”

我们现在可以回去专注于手头的问题,而不是讨论人们需要多长时间来测试这个问题。 现在就是这样,让我们​​拭目以待。

我很内疚直到最近才非常封闭地关注 master,所以这个反馈确实有点晚了。 不仅仅是程序员的问题(大多数 for 循环将在库代码中的函数内)恐怕这是教学的问题。 通常在函数或作用域之前教授 for 循环(当然,您需要了解作用域才能真正理解发生了什么,但在教学中通常会简化)。

在不解释函数或全局变量的情况下,教初学者如何对 1 到 10 的数字求和变得有点困难。

这是一个很大的问题。 在找出问题的真正原因后,令人惊讶的是它实际上很少出现。 大量 Julia 代码在野外和测试中的问题不大,它确实揭示了许多意外全局的变量(在根据原始 PR 的两个 Julia Base 测试中,我在大多数情况下都注意到了这一点DiffEq 的测试)。 在大多数情况下,似乎微妙的错误行为不是你得到的(期望循环中的变化),而是期望能够在循环中使用变量是我发现的绝大多数这显示在将测试脚本更新到 v1.0 时。 所以好消息是,在大多数情况下,用户会看到一个错误,并且不难修复。

不好的是,必须将global x放在循环内部有点冗长,现在您的 REPL 代码也与函数代码不同。 它是否比以前更直观的行为是一个强硬的意见,因为在硬/软局部范围界定中肯定存在一些边缘情况,因此这显然更容易解释。 但与此同时,虽然有比以前更简洁的解释,但现在更容易找到理解范围规则很重要的边缘情况。 🤷‍♂️。

我希望看到let阻塞的实验。 这将保留它的“你并不真正想要这么多全局变量”方面,以及简化的作用域解释,同时使 REPL 代码表现得像函数内部(这似乎是我们一直想要的)。 或者相反,让人们指定他们想要充当全局变量的变量

global x = 5
for i = 1:5
  println(x+i)
end

可能是保持明确性的好方法,并且会使“REPL 代码由于全局变量而变慢”更加明显。 缺点是再次将东西扔进函数不需要global标记。

但考虑到这往往是如何出现的,这并不是真正的游戏破坏或展示。 我将其归类为应该在任何研讨会中提及的疣,但并不是因为 v1.0 无法使用。 我希望改变这种行为不会被归类为破坏性的并且需要 v2.0。

我不太确定我喜欢 REPL 应该表现得像函数内部的想法。 显然不是,所以我希望它表现得像全局范围。 对我来说,REPL 的行为不像全局范围,可能比导致此问题的差异更令人困惑。

无论如何,至少我认为文档应该更明确地说明这个问题。 随便阅读文档,我会假设您需要使用local关键字来获取默认情况下在全局范围内发生的行为。

我希望看到let阻塞的实验。 这将保留它的“你并不真正想要这么多全局变量”方面,以及简化的作用域解释,同时使 REPL 代码表现得像函数内部(这似乎是我们一直想要的)

如果我们要“REPL 与函数内部相同”,我们还应该考虑outer

julia> i = 1
1

julia> for outer i = 1:10
       end
ERROR: syntax: no outer variable declaration exists for "for outer"

相对:

julia> function f()
          i = 0
          for outer i = 1:10
          end
          return i
       end
f (generic function with 1 method)

julia> f()
10

坦率地说,我对人们现在只提供这种反馈感到非常恼火。 这个变化已经在master上十个月了。

人们没有将 master 用于交互使用或教学,他们一直使用它来升级包,这些包受此影响很小,并且主要由有经验的程序员编写。

(不过,我是为数不多的在 #19324 中提供反馈的人之一,在那里我为旧行为辩护。)

一个非破坏性的方法是改回旧的行为(理想情况下不是通过插入隐式let块或任何东西——只需恢复julia-syntax.scm的旧代码作为一个选项) REPL。 或者更确切地说,要使其在可能需要它的 IJulia 等环境中可用,请将soft_global_scope=false标志添加到includeinclude_stringCore.eval以恢复旧行为。

(不过,我是为数不多的在 #19324 中提供反馈的人之一,在那里我为旧行为辩护。)

是的,我非常感激。 既然我们做出了选择,现在已经无所谓了,让它烤十个月,现在发布了它,并长期致力于稳定。 所以现在唯一要做的就是专注于未来要做的事情。

可以在旧行为和新行为之间进行选择很有趣,但感觉很麻烦。 这意味着我们不仅有时会出现每个人都认为非常令人困惑的范围界定行为,而且我们并不总是拥有它,我们是否拥有它取决于全局标志。 恐怕这感觉很不令人满意。

可以在旧行为和新行为之间进行选择很有趣,但感觉很麻烦。

如果有人实现了“unbreak me”软范围 AST 转换,那么在 IJulia、OhMyREPL 等中使用它会非常诱人,此时您会遇到更成问题的情况,即默认 REPL 被视为已损坏。

这不是我要说的。 显然,我们应该在所有这些上下文中使用相同的解决方案。 但是,将其作为范围规则的两种不同变体来实现似乎不如将其作为具有一组范围规则的代码转换来实现。 但也许这些在功能上是等效的。 但是,根据新的更简单的范围规则 + 接受 REPL 样式输入并在评估之前对其进行转换的转换,似乎更容易解释。

这可以作为Meta.globalize(m::Module, expr::Expr)来完成,它通过自动将模块中存在的任何全局变量注释为全局变量来转换表达式,如果它们被分配在任何顶级非函数范围内。 当然,我认为这与旧解析器所做的相同,但更透明一些,因为您可以自己调用Meta.globalize并查看 REPL 将评估什么。

这可以作为Meta.globalize(m::Module, expr::Expr)来完成,它通过自动将模块中存在的任何全局变量注释为全局变量来转换表达式,如果它们被分配在任何顶级非函数范围内。

几分钟前,我实际上开始考虑实施这样的事情。 但是,看起来作为julia-syntax.jl的选项实现会容易得多

  • 编写外部 AST 转换是可能的,但似乎有很多棘手的极端情况——你基本上必须重新实现范围规则——而我们已经在julia-syntax.scm编写了正确的代码。
  • 对于目前使用include_string来评估整个代码块并获取最后一个表达式的值的 IJulia 之类的东西,它甚至更加棘手。 我们不仅必须切换到逐个表达式解析表达式,而且可能需要一些技巧以保留原始行号(用于错误消息等)。 (虽然我找到了ChangePrecision.jl 的一个的东西。)
  • 更不用说include外部文件的情况了,您的 AST 转换不会捕获这些文件。

但是,根据新的更简单的范围规则 + 接受 REPL 样式输入并在评估之前对其进行转换的转换,似乎更容易解释。

我严重怀疑这会更容易向新用户解释,而不是仅仅说规则对于交互式使用或具有特定标志的include不那么挑剔。

这是globalize(::Module, ast)实现的粗略草案: https :

好的,我已经找到了如何实现globalize_include_string保留行号信息的函数,并将其添加到我的 gist 中

如果人们喜欢这种方法,那么一种可能的(不间断的)前进方式:

  1. 使用globalize等函数发布 SoftGlobalScope.jl 包。
  2. 在 IJulia(可能还有 Juno、vscode 和 OhMyREPL)中使用 SoftGlobalScope。
  3. 将 SoftGlobalScope 函数折叠到 REPL stdlib 包的未来版本中,并在 REPL 中使用它。

或者立即将其滚动到 REPL.jl 中是否可行? 我并不完全清楚 stdlib 更新在 1.0 中是如何工作的。

请看一下我的实现,以防我遗漏了一些会导致它变得脆弱的东西。

我们不能将它作为 1.1 中 REPL 的非默认功能吗?

#28523 和 #28750 的重复。 对于那些说他们不想教人们全局变量的人,我建议在for循环之前先教函数。 无论如何,函数更为基础,这将有助于设定代码应该用函数编写的期望。 虽然我理解其中的不便,但这种范围界定行为可以转化为教学优势:“实际上,全局变量是一个坏主意,尤其是在循环中使用它们,该语言使您不得不向后弯腰使用它们。”

不过,为此向 REPL 添加非默认功能对我来说似乎没问题。

@JeffBezanson ,请记住,在线性代数和统计学等技术课程中,我们中的许多人都希望使用 Julia 来代替 Matlab 等。 这些不是编程课程,学生通常没有编程背景。 我们从不进行结构化编程——它几乎都是与短片断和全局变量交互的。

此外,我首先使用动态语言的原因是在交互式探索和更严格的编程之间流畅地切换。 无法在全局和函数上下文中使用相同的代码是实现这一目标的障碍,即使对于习惯于定义概念范围的人来说也是如此,对于来自非 CS 背景的学生来说,情况更糟。

请记住,我们中的许多人都希望在线性代数和统计学等技术课程中使用 Julia 作为 Matlab 等的替代品。 这些不是编程课程,学生通常没有编程背景。 我们从不进行结构化编程——它几乎都是与短片断和全局变量交互的。

我们许多 Julia 用户的 CS 背景绝对为零(包括我自己),但在我看来,正确的态度(尤其是对学生而言)是愿意学习,而不是要求事情变得更糟以适应我们的天真。

现在,我并不一定意味着这个特殊的变化会变得更糟,因为我只有怎么在这里上的认识有限,但如果,这是一个显著并发症的情况下,或使其过于容易不必要写性能不佳的代码似乎不值得为了获得更好的讲座示例而进行更改。 你不能改变物理定律,让你向新生展示的静电学例子更适用于现实生活。

所以我作为一个也关心性能的非 CS 用户的问题是,如果这成为默认行为,我怎么可能搞砸。 从字面上看,这只是我们在这里看到的那些有问题的例子(我已经意识到了),还是我们可能经常以更微妙的方式把它搞砸?

就其价值而言,我确实同意让代码根据其封闭范围而表现不同是一个普遍不受欢迎的特性。

使代码更难交互地编写,迫使初学者编写他们的第一个循环来理解模糊的范围规则,并使从函数粘贴的代码在全局范围内不起作用,这无助于程序员在函数中编写快速代码。 它只会让交互使用 Julia 变得更加困难,对于初学者来说也更加困难。

我们不能将它作为 1.1 中 REPL 的非默认功能吗?

将“unbreak me”选项设为默认选项似乎更明智,尤其是针对初学者的选项。 如果它是一个非默认选项,那么最需要它的人将是那些没有启用它(并且不知道它存在)的人。

提议的 REPL 模式会对include ed 脚本做什么? 全局语句的评估是否取决于是否激活了 REPL 模式? 如果是这样,IMO 这将与 1.0 稳定性承诺不一致。

如果我们做了这样的事情,那么模块确定它是如何工作的似乎是有意义的。 所以Main将是一个“软范围”模块,而默认情况下其他模块将是“硬范围”模块。

我很想知道是否有可能对 REPL 进行猴子补丁以使用@stevengjglobalize函数,而且看起来它没有太多努力(虽然很hacky)。 见要点。 这不适用于 Juno(或任何其他直接调用Core.eval )。

不会向人们推荐这个,但是在进行快速而肮脏的数据分析时它对我非常有用。 我非常希望看到一个(经过深思熟虑的)解决方案,因为当您无法将函数中的代码复制并粘贴到 REPL 中以查看时,对于没有经验且通常不情愿的编码人员(即我的学生)来说,这确实令人困惑它做什么,反之亦然。

julia> a = 0                                                                
0                                                                           

julia> for i = 1:10                                                         
         a += i                                                             
       end                                                                  
ERROR: UndefVarError: a not defined                                         
Stacktrace:                                                                 
 [1] top-level scope at .\REPL[2]:2 [inlined]                               
 [2] top-level scope at .\none:0                                            

julia> using SoftGlobalScope                                                
[ Info: Precompiling SoftGlobalScope [363c7d7e-a618-11e8-01c4-4f22c151e122] 

julia> for i = 1:10                                                         
         a += i                                                             
       end                                                                  

julia> a                                                                    
55                                                                          

(顺便说一句:上面的测试和它所进行的一样多!)

提议的 REPL 模式会对包含的脚本做什么?

没有。 基本上,建议是这仅适用于在交互式提示下输入的代码。 一旦您开始将内容放入文件中,您就需要学习“硬范围”规则。 希望当您开始将代码放入文件时,您应该开始使用函数。

对于文件中的全局代码而言,比在提示符处有更挑剔的范围规则并不理想。 但我认为 #19324 与 Julia 1.0 稳定性承诺相结合,让我们没有理想的选择。

@stevengj

@JeffBezanson ,请记住,在线性代数和统计学等技术课程中,我们中的许多人都希望使用 Julia 来代替 Matlab 等。 这些不是编程课程,学生通常没有编程背景。 我们从不进行结构化编程——它几乎都是与短片断和全局变量交互的。

在为之前接触过 Matlab/R/... 的学生教授过使用 Julia 的课程后,我对这种担忧表示同情。 但与此同时,我不认为将 Julia 仅用作 Matlab 等替代品是一种可行的方法:正如 Discourse 和 StackOverflow 上的问题无数次所证明的那样,这可能导致难以修复和理解的性能缺陷,与投资于了解 Julia 与这些其他语言的不同之处,可能需要更大的成本(参见主题为“我从 Matlab 翻译此代码,但速度慢 10 倍”的帖子)。

我认为关键问题是无声的失败; 这个问题本身很容易理解和解决。 我建议保留新行为,但在Main给出警告(默认情况下;应该可以禁用它)。

对我来说,更大的问题是感知的不一致。 也就是说,我同意 Julia 的做法不同,但是:

  • 为什么从函数粘贴的代码在 REPL 中不起作用?
  • 我用过的其他语言都没有这种行为,这是采用的另一个障碍
  • 为什么for块与beginif块的行为不同? ( if我有点理解,但是一个块 [应该] 一个块。)。

关于第 2 条,我认为这比我们已经使用 Julia 一段时间(并致力于该语言)的人可能理解的要大得多。 我可以告诉你,我目前 0 比 7 说服我的团队用 Julia 编写代码; 其中两个是由于for循环问题引起的,我无法解释,因为我之前没有接触过它。 剩下的我想我们可以归结为我缺乏魅力。

我的偏好是确保从函数粘贴到 REPL 的代码与函数的行为相同,并且for循环在使用它们交互分析数据时会做预期的事情; 也就是说,具体来说,它们在没有任何特殊关键字的情况下改变外部/全局变量。

我不认为将 Julia 用作 Matlab 等替代品是一种可行的方法:正如 Discourse 和 StackOverflow 上的问题无数次所证明的那样,这可能导致难以修复和理解的性能缺陷,可能会导致更大的成本而不是投资于了解 Julia 与其他语言的不同之处。

对不起,但这个论点对我来说很荒谬。 我不是在谈论我教授编程的课程。 有一个地方可以进行简单的交互式计算,在非 CS 类中,通常将编程语言作为“美化计算器”引入。 在 Julia 中教授

如果你开始向学生介绍 Matlab 作为他们的“计算器”,那么过渡到“真正的”编程要困难得多,因为他们的第一直觉是在跳槽之前尽可能多地使用 Matlab,此时他们的坏习惯根深蒂固,他们不愿意学习一门新语言。 相比之下,如果您开始将 Julia 作为您美化的计算器,那么当需要进行更严肃的编程时,您将拥有更广泛的选择。 你不必训练他们把所有事情都塞进“向量”操作中,或者在他们做对之前强迫他们做坏事。

你是说我不应该在我的线性代数课程中使用 Julia 吗? 或者只有在我准备教授计算机科学和线性代数时才应该使用它?

我同意@stevengj的问题(向非程序员教学变得更加困难)和解决方案(使事情在 REPL 和各种 IDE 中工作)。 包含脚本仍然具有 Julia 1.0 范围规则,但这不是一个问题,只需要小心“我们可以将我们的 for 循环放在一个函数中,然后调用该函数”类之前“我们可以把我们的 for 循环在一个文件中并包含文件”类。

这听起来是一个很好的折衷方案,因为 REPL 上的交互式调试不会变得比它需要的更痛苦(或者让新用户更困惑),而脚本中的正常代码必须遵循严格的范围规则,并且不会被错误覆盖。意外变量。

你是说我不应该在我的线性代数课程中使用 Julia 吗? 或者只有在我准备教授计算机科学和线性代数时才应该使用它?

你可能误解了我的意思(或者我没有表达清楚)。 我说的是使用Julia 教授特定领域知识的课程(例如,我向经济学研究生教授数值方法),而不是 CS 课程(我没有这方面的经验)。

我试图说明的一点是,期望 Julia 和语言 X(可能是 Matlab)之间存在一定程度的差异是合理的; 相反,忽略这一点会(并且确实)导致问题。

就我个人而言,在学习一门新语言时,我更愿意尽早面对这些问题; 另外,我认为从长远来看,语言语义的简单性和一致性比与其他语言的相似性更重要。 但我承认这些偏好是主观的,理性的人可以有不同的偏好。

我已经创建了(未注册的)包https://github.com/stevengj/SoftGlobalScope.jl

如果这看起来合理,我可以继续注册该包,然后在 IJulia 中默认使用它(并且可能将 PR 提交给 Juno 等)。

我试图提出的观点是,期望 Julia 和语言 X(可能是 Matlab)之间存在一定程度的差异是合理的

明显地。 当我说“使用 Julia 而不是 Matlab”时,我并不是说我要在 Julia 中教他们 Matlab 语法,也不是专门针对以前的 Matlab 用户。

我更愿意早点面对这些问题

这与 Matlab 本身的差异无关。 我真的不想谈全球VS本地范围和效用global关键字进行静态分析,我第一次写在非CS的学生,还是第一次前面的环路的粘贴代码功能进入 REPL 以交互方式尝试。 我宁愿专注于我试图使用循环来表达的数学。

这里没有人仅仅因为这是 Matlab 用户的期望而争论软交互作用域。 偏离不熟悉的“范围”概念肯定会破坏您第一次展示循环的任何非 CS 讲座。 (而且即使对于有经验的用户,在我们交互工作时,强制添加global关键字也相当不方便。)

此处未提及的另一种修复方法是简单地停止使 'for' 定义作用域块(只是函数并让我们创建新的作用域)

@vtjnash ,我宁愿将讨论重点放在 Julia 2.0之前我们可以做的事情上。 我同意让交互模式表现不同只是权宜之计,我们应该认真考虑在几年内改变范围规则。

好点,这也需要import Future.scope 😀

(我认为这个模块/命名空间/行为效果已经被保留/存在)

在这里提醒一下,更改是为了确保代码在所有全局作用域环境中的行为都相同,而不管之前在该模块中评估过什么。 在此更改之前,只需将相同的代码运行两次或在文件中移动它,您就可以获得完全不同的答案(由不同的范围分配产生)。

在此更改之前,只需将相同的代码运行两次或在文件中移动它,您就可以获得完全不同的答案(由不同的范围分配产生)。

我在实践中看到的投诉数量(零)肯定比您将看到(并且已经看到)当前行为的投诉和困惑数量相形见绌。

在此更改之前,只需运行两次相同的代码,您就可以获得完全不同的答案(由不同的范围分配产生)

你的意思是在下面的代码中, a在第一个和第二个for循环之间变化吗? 在我看来,这是预期的行为,而不是错误。

a = 0
for i = 1:5
  a += 1
end

for i = 1:5
  a += 1
end

提议的 REPL 模式会对包含的脚本做什么?

@mauro3 @stevengj我想添加一个函数(比如exec("path/to/script.jl") )可以通过一个小版本的碰撞来完成? 我们还可以警告exec来自exec 'ed 脚本的另一个文件,然后在那里放置一些教学信息以推动他们使用include

昨晚我在试图围绕这个问题(又一次)试图找出最佳行动方案时写下的一些想法。 没有结论,但我认为这很清楚地说明了问题。 这个问题思考了几年,我认为没有任何“理想的解决方案”——这可能是那些只有次优选择的问题之一。


人们天真地认为全局作用域是一种有趣的封闭局部作用域。 这就是全局作用域在 Julia 0.6 及更早版本中的工作方式的原因:

  • 如果外部局部作用域创建了一个局部变量,而内部局部作用域为其赋值,则该赋值会更新外部局部变量。
  • 如果外部全局作用域创建一个全局变量并且内部局部作用域分配给它,则该分配先前更新了外部全局变量。

但是,主要区别在于:

  • 根据设计,外部局部变量是否存在并不取决于外部局部作用域中表达式的出现或执行顺序。
  • 然而,全局变量是否存在不能与顺序无关,因为一个人在全局范围内评估表达式,一次一个。

此外,由于全局作用域通常很长——经常分布在多个文件中——具有表达式的含义取决于与其任意距离的其他表达式,这是一种“远距离幽灵动作”效果,因此,非常不受欢迎.


最后一个观察结果说明了为什么在全局范围内 for 循环的两个不同版本的行为不同是有问题的:

# file1.jl
for i = 1:5
  a += 1
end
# file2.jl
a = 1



md5-f03fb9fa19e36e95f6b80b96bac9811e



```jl
# main.jl
include("file1.jl")
include("file2.jl")
include("file3.jl")

另请注意, file1.jlfile3.jl是相同的,我们可以通过两次包含相同的文件来简化示例,每次都具有不同的含义和行为。

另一个有问题的情况是长时间运行的 REPL 会话。 试试网上某个地方的例子? 它失败是因为您碰巧有一个与示例在 for 循环或类似构造中用于局部变量的名称相同的全局变量。 因此,认为新行为是唯一会引起混淆的行为绝对是不准确的。 我同意新行为是 REPL 中的可用性问题,但我只是想缓和对话并在这里清楚地展示另一方。

我的小建议,不处理 repl 问题,但在非交互式教学语言时对教学目的很有用,至少:定义一个名为“program”的主块,就像可以在 fortran 中完成(它是与上面的“让...结束”相同,只是使用更自然的符号):

程序测试
...
结尾

人们可以在不深入研究范围细节的情况下教授语言,并最终只讨论这一点。

另一个有问题的情况是长时间运行的 REPL 会话。 试试网上某个地方的例子? 它失败是因为您碰巧有一个与示例在 for 循环或类似构造中用于局部变量的名称相同的全局变量。

心烦意乱的用户对此提出了多少邮件列表投诉和 github 问题? 零,据我所知。 为什么? 可能是因为这种行为对人们来说根本不足为奇——如果你在全局范围内工作,你依赖于全局状态。

因此,认为新行为是唯一会引起混淆的行为绝对是不准确的。

我认为这是一个错误的等价 - 这里潜在混淆的程度存在a ,你在这里改变了它。” 在 Julia 1.0 中,老实说,我很担心如果我正在听线性代数讲座并且不得不在从未听过这个词的学生面前神秘地输入global关键字时我会怎么做CS意义上的“范围”。

我们应该认真考虑在几年内改变范围界定规则。

绝对不。 你真的想回到循环范围的 v0.2 之前的世界(见 #1571 和 #330)吗?

实际上,我们从未完全支持将代码从函数逐行复制并粘贴到 REPL 中。 因此,我们可以将此视为实现这一目标的机会。 具体来说,虽然它对for循环“有效”,但它不适用于内部函数:

x = 0
f(y) = (x=y)

在函数内部, f将改变第一行中的x 。 在 REPL 中不会。 但是通过 SoftGlobalScope.jl 中的转换,它可以工作。 当然,我们可能不希望默认情况下启用它,因为这样粘贴独立函数定义将不起作用。 首先想到的是用于逐行函数调试的 REPL 模式。

你真的想回到 v0.2 之前的世界吗

不,我要回到0.6的世界。 😉

我想我更多的是回应:

此处未提及的另一个修复方法是简单地停止使用“for”定义范围块

实际上,我们从未完全支持将代码从函数逐行复制并粘贴到 REPL 中。 因此,我们可以将此视为实现这一目标的机会。

我非常感谢这种情绪,对于我的用例,它真的很有帮助。 从我的角度来看,这实际上是让 REPL 尽可能有用,而不是直接更改语言的范围规则。

也就是说,我对这个问题考虑得越多,我就越能看到我(个人)对 REPL 应该做什么持有的相互矛盾的观点。

具体来说,如果 REPL 匹配函数体的作用域规则,我会非常喜欢它; 即,变量是局部的而不是全局的,您可以直接从函数中复制和粘贴代码,并且知道它会起作用。 我想一个天真的实现将类似于表单的 let-block 包装(如前所述)

julia> b = a + 1

被转化为

let a = _vars[:a]::Float64 # extract the variables used from the backing store
    # Code from the REPL
    b = a + 1
    # Save assigned variables back to the backing store
   _vars[:b] = b
end

正确地完成(即,由知道他们在做什么的人),我想这将比现有的 REPL 有很多好处。 1. 以前具有交互式数据分析/计算的工作流程才有效。 2. 在 Discourse 上基本回应是“停止使用全局变量进行基准测试”的帖子要少得多——一切都将是本地的,所以希望很快! :) 3. 复制和粘贴到/从函数体按预期工作。 4. 如果后备存储是某种 Dict,则类似workspace()函数是微不足道的; 把它清除掉。 5. 全局变量变得明确——事物都是局部的,除非你特别要求它们是全局的; 从我的角度来看,这是一个很大的优势,我不喜欢隐式创建全局变量。 一个非常小的最后一点(我犹豫要不要添加这个!),这将匹配 Matlab 的行为,使人们更容易转换 - 在 Matlab REPL 中,除非明确注释为全局变量,否则所有变量似乎都是局部的。

直到几个小时前,这个故事对我来说还不错。 但是在 Jeff 关于函数的评论之后,我考虑粘贴独立的函数定义以及这种方法如何基本上防止这种情况,因为函数定义应该进入全局范围(至少,这可能是预期的); 但随后如果他们目的

选择SoftGlobalScope.jl的中途之家可能最终成为最不令人困惑的妥协,但我担心它只是要记住的另一套规则(哪些东西在 REPL 中有效,但在我的函数体/全局范围内无效,并且反之亦然)。

为长篇文章道歉,但我认为这对可用性很重要(它帮助我思考!)。

心烦意乱的用户对此提出了多少邮件列表投诉和 github 问题? 零,据我所知。 为什么? 可能是因为这种行为对人们来说根本不足为奇——如果你在全局范围内工作,你依赖于全局状态。

嗯,你真的系统研究过这个吗? 我一定是错过了。 然而,这并不意味着此行为不是错误或意外结果的来源; 只是在用户弄清楚之后,它被认为是正确的行为,因此没有提示问题/投诉。

在 Julia 1.0 中,老实说,我很担心如果我正在听线性代数讲座并且不得不神秘地输入全局关键字,我会怎么做

我很同情这个问题。 当我教经济学生一些简单的编程课程时,我通常建议他们在将代码包装在函数中,简单地注释掉functionend和运行东西之间来回切换在全局范围内,因此他们可以检查正在发生的事情。 这几乎弥补了当时 Julia 缺乏调试基础设施的不足。

这种方法似乎不再可行。 但我想知道这是否真的是正确的方法,同时各种事情都得到了很大改善(#265 已修复, Rebugger.j和最近的Rebugger.j大大改进了工作流程/调试)。

似乎这个问题并不太困扰有经验的用户,主要关注的是教学环境中的混乱。 我自己还没有尝试过这个,但我想知道我们是否可以调整我们的教学方法,例如在循环之前引入函数,避免在全局范围内循环。 无论如何,这些都是良好风格的元素,将使学生受益。

只是一个小提示:虽然对 REPL 的全局范围进行了特殊的封装,将允许将代码复制粘贴到函数中和从函数中复制粘贴,但它不允许复制粘贴到/从另一个模块的全局范围复制粘贴。

我想知道我们是否可以调整我们的教学方法,例如在循环之前引入函数,避免全局范围内的循环。

这在不专注于教授编程的课程中是完全不切实际的。 如果我不能以交互方式使用它和/或必须首先为所有内容编写函数,我最好不要在我的课程中使用它。

(这不仅仅是教学法。全局范围内的循环对于交互式工作很有用。人们喜欢用于技术计算的动态语言的主要原因之一是它们的交互式探索设施。并非所有编码都以性能为导向。)

多年来,人们对旧的“软/硬范围”区别感到困惑或抱怨的话题和问题有很多,因此声称没有人对旧的行为感到困惑或抱怨只是……不对。 我可以挖掘其中的一些,但你在附近, @stevengj ,所以你可以很容易地挖掘它们,我很难相信你没有注意到或不记得这些抱怨和对话。

@StefanKarpinski ,我特别指的是抱怨全局循环取决于全局状态的人。 我不记得有人抱怨这是不好的行为,我也找不到任何这样的例子。

我同意人们对赋值定义新变量的时间和地点感到困惑,但通常是相反的——他们希望局部作用域更加全局化(而不是相反),或者不区分beginlet 。 IIRC,抱怨从来没有在全局循环中分配给全局变量会产生修改全局变量的令人惊讶的副作用。

范围界定的整个问题让新用户感到困惑,并且将继续如此。 但令人困惑的部分不是分配给全局变量名称会影响全局状态的情况。 当前的行为使情况变得更糟,而不是更好。

@StefanKarpinski :我有一种感觉,以前,软/硬范围的混淆更多是理论性质的(人们阅读手册),而不是实际(人们得到意想不到的结果)。 这对我来说绝对是这样,例如,这里的搜索结果支持这一点; 我在这里找到了一个反例。

另一方面,这种新行为不会让人们在阅读手册时感到困惑,而是在使用 REPL 时。 可以说后者更糟。

SoftGlobalScope.jl现在是一个注册包。 我的目的是在默认情况下(选择退出)为 IJulia 启用它,至少在本学期是这样。

@ mauro3 ,即使你的“反例”也是关于有人被范围而不是软范围混淆的。 在 0.7 中使更多范围变得“困难”肯定会造成更多这种混乱。

我要指出的是,IJulia 有一个有趣的可能性,即默认情况下将变量设为局部 do 块。 即,如果您在单个块中执行此操作,则它可以工作:

t = 0
for i = 1:n
    t += i
end
t

... 并且t仅在此评估块中可见。 如果您希望它在外面可见,则必须执行以下操作:

global t = 0
for i = 1:n
    global t += i
end
t

我还考虑了 Julia 的类似方法,其中块是文件而不是模块。 换句话说,只是在顶级范围内执行t = 0会创建一个文件本地变量而不是全局变量。 要声明一个真正的全局变量,您需要编写global t = 0 ,然后该变量将在整个模块中可见。 也许太奇怪了,但这些年来我已经多次想到了。

默认情况下,IJulia 有一个有趣的可能性,可以将变量设为本地 do 块

@StefanKarpinski ,我认为这会更令人困惑,并且与笔记本的通常使用方式背道而驰。 在多个单元格中使用/修改相同的变量是很常见的,因此需要为所有单元格间变量使用global关键字对我来说是一个不切实际的问题——它需要对范围概念的讨论甚至比问题还要我们一直在讨论for循环。

我认为只要我们都同意——正如我们似乎所认为的那样——这主要或完全是一个互动问题,那么我们就有了前进的道路。 如果我们在 REPL 中对此进行特殊处理(就像对 IJulia 所做的那样),唯一不好的情况是在 REPL 中开发某些内容,然后将其移至顶级脚本代码。 可以说这就是你应该引入函数的地方,所以我不认为它那么糟糕。 REPL 和函数体之间的复制粘贴代码将起作用(大多数情况下),这可能已经足够了。

然后我们还可以选择通过使 REPL 变量以某种方式成为 REPL 的本地变量来进一步证明/澄清区别——即不是普通的全局变量,不能作为Main.x 。 这与上面刚刚提出的@StefanKarpinski非常相似,但在所有输入块/单元之间共享。

从实际的角度来看,在 REPL 中得到这个“修复”并不是
仅对教学/非程序员用户很重要。 这种行为也
通过 REPL(通过复制粘贴部分)进行交互式调试
不切实际。 这种调试模式有时可能更可取(即使对于
好的调试器和)即使对于有经验的程序员(并且通常是其中之一)
喜欢动态语言的原因)。 当然对于有经验的
程序员,可选应该不是问题; 对于新手用户来说
最好是默认值。

@StefanKarpinski
作为一个天真的程序员,我真的不明白在查看
全局作用域是一种有趣的封闭局部作用域,尤其是在动态中
语言。 我确实明白,从编译器的角度来看,它不是
肯定是正确的(在 Julia 中),但它是一个不错的、简单的和有用的模型
对于(天真的)程序员。 (我也怀疑它实际上可能以这种方式实施
某些语言)。
Julia 似乎也以这种方式向程序员展示了它:
以下函数函数将给出错误“a not defined”,其中
如果将 a=1 放在 for 循环之前,则不会这样做。

功能测试()
对于 i = 1:10
a=a+i
结尾
a=1
@显示一个
结尾

除非我完全误解,否则这似乎与“是否
外部局部变量存在,按照设计,不依赖于顺序
外部局部作用域中表达式的出现或执行”。

我非常同意避免“幽灵般的远距离行动”,并且非常同意
更喜欢在函数/调用堆栈中使用全局变量的显式定义
级别,个人也喜欢从文件中加载类似的东西
它自己的范围,并且需要明确定义使用全局变量。
不过,在循环级别对我来说有点远,因为
定义/上下文通常非常接近。
3 个文件的示例有点做作(并且失败并出现预期的“a not
定义”错误):您通常会将初始定义放在相同的
文件。
这里面有真正的诡异危险(我被它咬了
在其他语言中)其中包含在全局范围内运行,因此您
无意中定义了一个全局变量,可能会干扰其他
代码。 但是,必须在循环中使用 global 不是解决方案
这个问题。

写入长时间运行的 REPL 会话:
当前行为取代了一种非常罕见且易于发现的故障模式
用于在 REPL 中运行在线示例(您错过了复制/粘贴
循环前变量的初始定义,并且已经有了
从以前的东西全局定义的相同变量)而不是
如果它是函数的一部分,则能够正确运行在线示例
(没有在任何地方添加全局),如果是,则不解决问题
不是(如果在线代码中已经存在 global ,您仍将使用
已经存在的全局变量中的错误值)

我应该早点调整到这一点,但经过短暂的关注之后,一切似乎都很好。

我们实际上从未完全支持将代码从函数逐行复制并粘贴到 REPL 中……首先想到的是用于逐行函数调试的 REPL 模式。

事实上,Rebugger(正是那个)在 1.0 上正常工作只是因为它缺少 0.7 的范围弃用,并且永远无法在 0.6 上工作。 但是,我很高兴能够验证 SoftGlobalScope.jl 似乎没有破坏它。 例如,如果你深入到show([1,2,4])你会得到这里:

show_delim_array(io::IO, itr::Union{SimpleVector, AbstractArray}, op, delim, cl, delim_one) in Base at show.jl:649
  io = IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting))
  itr = [1, 2, 4]
  op = [
  delim = ,
  cl = ]
  delim_one = false
  i1 = 1
  l = 3
rebug> eval(softscope(Main, :(<strong i="10">@eval</strong> Base let (io, itr, op, delim, cl, delim_one, i1, l) = Main.Rebugger.getstored("bbf69398-aac5-11e8-1427-0158b103a88c")
       begin
           print(io, op)
           if !(show_circular(io, itr))
               recur_io = IOContext(io, :SHOWN_SET => itr)
               if !(haskey(io, :compact))
                   recur_io = IOContext(recur_io, :compact => true)
               end
               first = true
               i = i1
               if l >= i1
                   while true
                       if !(isassigned(itr, i))
                           print(io, undef_ref_str)
                       else
                           x = itr[i]
                           show(recur_io, x)
                       end
                       i += 1
                       if i > l
                           delim_one && (first && print(io, delim))
                           break
                       end
                       first = false
                       print(io, delim)
                       print(io, ' ')
                   end
               end
           end
           print(io, cl)
       end
       end)))
[1, 2, 4]

所以它在 1.0 上运行良好(有或没有softscope )。 在 0.7 上,评估这个(有或没有softscope )将产生

┌ Warning: Deprecated syntax `implicit assignment to global variable `first``.
│ Use `global first` instead.
└ @ none:0
┌ Warning: Deprecated syntax `implicit assignment to global variable `first``.
│ Use `global first` instead.
└ @ none:0
[ERROR: invalid redefinition of constant first
Stacktrace:
 [1] top-level scope at ./REBUG:9 [inlined]
 [2] top-level scope at ./none:0
 [3] eval(::Module, ::Any) at ./boot.jl:319
 [4] top-level scope at none:0
 [5] eval at ./boot.jl:319 [inlined]
 [6] eval(::Expr) at ./client.jl:399
 [7] top-level scope at none:0

所以 0.7/1.0 绝对是一个进步,如果softscope在不破坏重要功能的情况下使某些事情变得更容易,那就太好了。

因此,最大的问题是如何在不影响其他软件包的情况下适当地拦截它(https://github.com/stevengj/SoftGlobalScope.jl/issues/2)。

@timholy:(<strong i="6">@eval</strong> ...)受到保护。

似乎与“是否
外部局部变量存在,按照设计,不依赖于顺序
外部局部作用域中表达式的出现或执行”。

(外部)局部变量a存在,但尚未分配。 如果循环在读取之前尝试分配给a ,则该分配也将在外部可见。

通常,创建变量绑定和为其分配值是单独的步骤。

这方面的时间表是什么? 看来这将是对用户可用性的巨大改进。 在 Julia 发布 1.0 的这个“关键”时刻,尽快修复这个问题(按照上面 Jeff 建议的方式)并标记一个新的 Julia 版本或 REPL 版本似乎是有利的。 (对不起,这个扶手椅评论,因为我当然不会解决这个问题!)

@杰夫贝赞森
我想争辩说,虽然这是真的(对于实现/编译器),但天真的 julia 程序员无法从他的更简单的概念模型(一个变量在它被定义的那一刻开始存在)看不到任何不同的行为。 不幸的是你是对的,下面的代码不会出错,而如果你在最后省略 a=2 会出错
功能测试()
对于 i = 1:10
a=1
结尾
打印(一)
a=2
结尾
不幸的是,我将解释一下:我可以理解这种行为(因为我以前使用过编译语言),但仍然发现它令人困惑和出乎意料。 对于只有脚本经验或编程新手的人来说,这有多糟糕。 另外,我发现了一些显示行为的代码,我没有看到有用的应用程序(也许你可以帮助我)

在 REPL 上:
我只是更加确信,至少在 REPL 中将作用域改回“正常”(无需在循环中添加全局)是高度优先的:我今天在 REPL 中测试了一些东西并(再次)被它咬住了,需要一些时间来实现它。 鉴于我已经关注 Julia 一段时间了,真的很喜欢它,我什至在关注这个关于这个问题的线程,我什至称它为“showstopper”:测试该语言的新手(对 Julia)很可能找不到解决问题,然后放弃。

@jeffbezanson和我都在期待已久的假期(我不应该读这个)。 我们可以在一周左右的时间内弄清楚该做什么。

@derijkp ,虽然对反馈表示赞赏,但范围规则不适合更广泛的辩论或修订。 桌子上唯一的东西是特殊外壳交互式评估。 SoftGlobalScope 包已经是一个优秀的实验性实现,它可能只是制作 Base 的那一部分并在 REPL 中使用它的问题。

@derijkp一个简短的回答是,我认为如果变量的范围对应于某个块构造(例如函数或循环的主体),

是的,我可以相信这与某些人的直觉不符。 但是您只能在使用语言的前十分钟进行优化。 真正的问题是,教授/学习它的工作原理有多难,从长远来看,哪种设计会节省时间(通过使语言更简单,更容易开发工具等)?

(与上述关于修改 REPL 行为的大部分内容一致)
我希望看到 REPL 不会导致这个 stackoverflow 问题
最好尽快,因为许多新眼睛都在看着朱莉娅

我同意......并且还认为范围规则不一定要改变,只是所有的交互界面(即 REPL、Jupyter 和 Juno 控件输入)

这不仅仅是初学者学习新规则的问题。 如果您不能将代码片段复制并粘贴到 REPL、jupyter 等以及函数中,这对中级程序员来说也是一个主要的烦恼。

当然,我也同意其他海报......对于初学者来说,他们将把他们在函数中看到的代码片段复制到脚本中,并且当它在函数内部复制时没有相同的行为时会感到完全困惑,在 juno 中,repl 和 jupyter。 将有 100 个堆栈交换问题归结为同一问题。 中级程序员将拥有各种自行开发的解决方案,其中包含let块等,这会进一步混淆事情

将有 100 个堆栈交换问题归结为同一问题。 中级程序员将拥有各种自行开发的解决方案,其中包含let块等,这会进一步混淆事情

可能,但在现阶段,这是假设性的(所链接的问题的 OP 也在询问范围规则的基本原理,而不是对此感到困惑)。

此外,虽然我尊重每个对此有顾虑的人的教学经验,但这是否会成为课堂上的一件大事是时间会证明的。

提问者似乎对此感到困惑:“我想知道这对初学 julia 用户是否直观。对我来说并不直观......”

提问者似乎对此感到困惑:

更不用说这是一个对编程语言有足够了解以理解范围细微差别的人。 所有对这些主题完全一无所知的 matlab 类型用户怎么办......,并且可能永远不会投入足够的时间来理解细微差别。

可能,但在现阶段这是假设

我已经在 stackoverflow 上回答了多个与此相关的问题,主要是新用户提出的,在现实生活中甚至更多(昨天的最后一个,来自 Matlab 用户,他认为这是不行的)。

将有 100 个堆栈交换问题归结为同一问题。

在我的“业余时间”中,我一直在向 SE 问题添加scopescopingglobal-variables标签。 我只是因为没有时间而停下来,而不是因为没有更多。

经过大量讨论(包括分类)后的结论:我们将在 Base 中包含类似于SoftGlobalScope ,并在 REPL 和所有其他交互式评估上下文中使用它。 @JeffBezanson指出,它的实现方式实际上与以前实现软作用域的方式基本相同,因此在某种程度上我们正在绕圈子。 不同之处在于,现在模块或脚本中没有作用域行为,只有在类似 REPL 的上下文中。 我还认为 _explaining_ 软作用域作为源代码重写比试图区分硬作用域和软作用域更清晰(我们从来没有解释过杰夫,我可能会指出)。

这两个陈述让我有点困惑,因为它们看起来有点矛盾:

并在 REPL 和所有其他交互式评估上下文中使用它

[...] 脚本中没有作用域行为,只有在类似 REPL 的上下文中。

这是否意味着模块Main有时具有软作用域(例如在 REPL 提示符下),有时具有硬作用域(例如在julia -L script.jl )? 说Main总是具有软作用域是没有意义的吗? 并且模块可以通过using SoftGlobalScope选择加入软范围?

(我猜)脚本中的范围规则不能更改,因为它会向后不兼容,即会破坏为 1.0 编写的任何代码将在任何 1.* 版本上运行的承诺。 你是对的,尽管 REPL 的范围界定同样的问题也适用于脚本(天真的用户完全失去了为什么他/她的代码在作为脚本运行时无法正常工作)。 在没有重大不兼容的情况下解决/缓解此问题的一种方法是向 julia cmdline 添加一个选项以使用 softscope(或替代),例如 julia -f programfile,并在初学者可能会使用的任何描述/教程中显示此选项遇到。
我还看到了 softscope 的潜在替代方案,它可能有一些优点(尽管我可能忽略了缺点): 如果文件(被调用的脚本)总是引入自己的本地范围怎么办:范围规则将与功能齐全,受到了广大用户的期待。 它还可以消除新用户的许多性能负担:
没有更多不需要的全局变量(必须明确定义全局变量),并且可以编译代码
(你有多少次说要把所有东西都放在一个函数中,并避免使用全局变量?)

我刚刚击中了这个,老实说完全无法理解,以前从未用任何其他语言见过它。 我计划在今年晚些时候在我的大学中为高级 R 用户引入可选的 Julia 课程,一旦事情稳定下来,我的学生将在第 0 天开始在 REPL 中随机输入内容时点击此课程。 事实上, for循环与if语句的行为不同,这只是在伤口上撒盐,但在范围界定方面这可能是合乎逻辑的。 函数内部的范围很难让生物学学生掌握,必须在 REPL/脚本/for 循环/if 语句中解释_尽管感知_明显不一致的想法(因为这就是我们正在谈论的关于这里)以一种不同于地球上其他所有语言的方式让我感到非常难过。

我理解做出的向后兼容性承诺,但是让这项工作_正如地球上每个非 cs 人员(以及我怀疑的大多数 cs 人员)所期望的_似乎是一个错误修复而不是向后兼容性问题 - 我们不是说每个错误都会被永远重现,是吗? REPL 修复显然是必不可少的,所以你提出这个建议很好,但随后不得不解释你不能将脚本复制到 REPL 中,并期望相同的行为看起来与原始问题一样糟糕或更糟。

请,请,请考虑将此视为错误修复并使用脚本和 REPL 将其推出 - 即使有切换到“旧”行为 - 并尽快在 1.0.1 中执行此操作。

我试图去学习 julia 的一位同事也遇到了这个问题。 必须在第一步解释整个全局变量与局部变量的事情并不理想......

我不认为将其视为“错误修复”是可行的,因为它会破坏 1.0 的稳定性合同。 但是,对我来说,对以julia -i (即“交互”模式)运行的脚本使用 softscope 似乎是合理的。

(也就是说,会有一个标志--softscope={yes|no}并且它会默认为isinteractive 。)

我们将不得不考虑脚本模式的选择。

就此而言,对于任何“脚本”(即julia foo.jl默认为--softscope=yes julia foo.jl ,并且只打开模块和include的“硬”范围规则,这对我来说并不疯狂

就此而言,对于任何“脚本”默认为 --softscope=yes 对我来说并不疯狂,

那。 另一个需要认真考虑的是朱诺。 请记住,人们将通过他们的代码<shift-enter>进行交互式开发(尤其是在使用回归测试时),然后希望能够运行相同的文件。 代码是否在@testset是否重要(我认为这可能会引入一个范围)? 如果相同的文本在@testset更改而不是在使用 Atom 的集成时更改,并且与] test不一致,那么用户将非常困惑。

在我看来,最好的解决方案是硬范围只是一个选择加入的东西,如果其他用法(包括脚本中的include )使用softscope除非你另有说明.

不同于地球上的所有其他语言

你想写var x = 0来介绍每个变量吗? 这也将“修复”这个问题,并且更像其他语言。

我们并不是说每个错误都会被永远重现,是吗?

这不是它的工作原理。 仅通过将当前行为称为错误,您无法对所需的语言进行任何更改。

我真的不认为应该有一个命令行选项。 然后,每段 julia 代码都必须带有注释或告诉您使用哪个选项的内容。 源文件中的某种解析器指令会好一点,但更好的是有一个固定的规则。 例如,模块内的硬作用域可能才有意义。

让我再次尝试对此进行解释,这可能有助于避免人们在课堂上看到的狂热、歇斯底里和屠杀:


Julia 有两种变量:局部变量和全局变量。 您在 REPL 中或在顶层引入的变量,在其他任何事物之外,都是全局的。 在函数和循环中引入的变量是局部的。 在程序中更新全局变量通常是不好的,所以如果你在一个循环或函数中并且想要更新一个全局变量,你必须通过再次编写global声明来明确说明它。

也许可以改进; 欢迎提出建议。 我知道,你宁愿根本不需要任何解释。 我明白了。 但对我来说似乎并没有那么糟糕。

我真的不认为应该有一个命令行选项。 然后,每段 julia 代码都必须带有注释或告诉您使用哪个选项的内容。 源文件中的某种解析器指令会好一点,但更好的是有一个固定的规则

我同意。 对我来说,这听起来像是教学和沟通方面的难题。

例如,模块内的硬作用域可能才有意义。

我的理解是:如果我在从 IJulia 笔记本复制的.jl文件中有一个简短的脚本(不在模块中!),那么如果我直接在 REPL 中运行该代码,或者使用 shift-进入 Juno,那么它的行为将始终如软作用域一样......但是如果我复制它而不是module块,那么它会因为全局变量而对我大喊大叫? 但是如果我在模块内的函数内复制了该代码,那么它应该可以工作。

如果是这样,那就完全有道理了,非常有教益和连贯性。 顶级脚本是用于探索等的交互式界面,但您永远不会将这种代码放入模块中。 模块是你应该填充的东西,函数是非常仔细考虑的全局变量。 告诉人们这些规则很容易。

你想写 var x = 0 来介绍每个变量吗? 这也将“修复”这个问题,并且更像其他语言。

不,我宁愿不! 但是具有 REPL 的脚本语言很少这样做(例如 ruby​​、python、R 等),它们的行为就像 Julia v0.6 那样。

Julia 有两种变量:局部变量和全局变量。 您在 REPL 中或在顶层引入的变量,在其他任何事物之外,都是全局的。 在函数和循环中引入的变量是局部的。 在程序中更新全局变量通常是不好的,因此如果您在循环或函数中并想要更新全局变量,则必须通过再次编写全局声明来明确说明它。

我完全理解你在这里所说的,我不会(碰木头!)再犯这个错误。 但我担心的整个问题都不是我。 当我解释函数内部的变量无法看到外部的变量时,我发现引入作用域相对容易(没有直接提及),反之亦然(尽管这更像是一种愿望而不是 R 中的事实!),因为函数本身已经是一个_相对_先进的概念。 但这在学习曲线中发生得更早,我们不希望任何像范围这样复杂的事情影响人们......

还要注意,不仅是“在 REPL 中或在顶层引入的 _variables 是全局的”和“在函数和循环中引入的 _variables 是局部的_”,还包括 REPL 中的 if 语句中的变量或在顶层是全局的,但@testset中的变量是局部的。 我们最终陷入了“无论是本地还是全球,只要尝试并为自己解决,祝你好运”的困境。

但是,我同意@jlperla - “模块内部的硬范围可能才有意义”的提议对我来说似乎完全没问题! 模块又是一个足够先进的概念……如果软作用域适用于 REPL 和脚本,那绝对没问题。

我们不希望任何像范围这样复杂的事情影响人们......
顶层是全局的,但@testset中的变量是局部的

我想说的是,我觉得对全球与本地的简单描述对于早期教学就足够了——你甚至不需要说“范围”这个词(它根本不会出现)在我上面的解释中)。 当您只是在 REPL 中展示一些简单的表达式和循环时,您并不是在教人们有关测试集的知识,并且您不需要语言中所有内容的范围行为的详尽列表。

我唯一的观点是,这种变化并不会突然使预先教授大量有关语言的细节成为必要。 您仍然可以忽略关于范围、测试集等的绝大多数内容,关于全局与本地的简单行就足够了。

一条关于 global 与 local 的简单行就足够了。

在一个每个人都从头开始编写所有代码的世界里,我完全同意。

问题是,您不仅需要教学生有关范围的知识,还需要了解他们从何处复制粘贴的代码的范围。 你需要教他们,如果他们在函数或 let 块中复制粘贴堆栈交换上的代码,他们需要扫描它并找到添加“全局”的位置(如果他们将其粘贴到 REPL 或.jl文件。 但是,如果他们将该代码复制到函数内或 Jupyter 笔记本中。 他们不应该。 如果他们在包含全局变量的 stackexchange 或教程页面中找到代码,但他们想在自己的函数中复制和修改该代码,那么他们需要去除全局变量。

然后学生们开始问为什么for创建这个他们需要担心的范围而不是其他事情......

我们最终陷入了“无论是本地还是全球,只要尝试并为自己解决,祝你好运”的困境。

流行测验:在 julia 0.6 中,是x全局还是本地:

for i = 1:10
    x = i
end

答案是没有办法知道,因为这取决于之前是否定义了全局x 。 现在,您可以肯定地说它是本地的。

伙计们,这种讨论即将不再富有成效。 Jeff 非常清楚 REPL 中的旧行为很好。 您认为谁首先设计并实施了它? 我们已经致力于改变交互行为。 仍然需要决定“脚本”是否是交互式的。 当你称它为“脚本”时,它听起来是交互性的,但当你称它为“程序”时,它的交互性听起来要低得多——但它们是完全一样的。 请保持简短和有建设性的答复,并将重点放在仍然必须决定的事情上。 如果有与此不同的评论,它们可能会被隐藏并且该线程可能会被锁定。

一个想法我有但我们认为“太烦人”和“可能导致村民拿出干草叉”是在非交互式环境中,我们可能需要localglobal “软作用域”中的

当我第一次被介绍到 Julia 时(不久前,我主要来自 Fortran 背景),我被教导“Julia 是在函数级别编译且速度很快,因此所有必须高效的事情都必须在函数内部完成. 在主要的“程序”中,它的行为就像一种脚本语言”。 我发现这很公平,因为我无法想象有人在不理解该语句的情况下会做任何对计算要求太高的事情。 因此,如果在主程序中使用与函数中相同的符号和结构而在性能上有任何牺牲,我发现这是完全可以接受的,比试图理解和教授这些作用域规则而不能复制和将代码从一处粘贴到另一处。

顺便说一下,我还是 Julia 的新手,选择它来教一些高中和本科生一些物理系统模拟的基础知识。 我已经让这个问题恢复到以前版本的“正常”行为,因为它让我们很头疼。

此对话现已锁定,只有 Julia 提交者可以发表评论。

@JeffBezanson ,最初仅在 REPL 中并在其他地方选择加入,您在此讨论主题中建议的语义的实施计划是什么?

听起来您打算将其直接放入降低代码 ( julia-syntax.scm ) 中,而不是作为语法重写 ala SoftScope.jl? 或者您更愿意先将其作为语法重写(将 SoftScope 修改为建议的规则并将其转换为 stdlib),然后推迟将其放入较低的代码中以供以后的 Julia 版本使用?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

yurivish picture yurivish  ·  3评论

StefanKarpinski picture StefanKarpinski  ·  3评论

sbromberger picture sbromberger  ·  3评论

musm picture musm  ·  3评论

i-apellaniz picture i-apellaniz  ·  3评论