Julia作用域细节:在循环中定义闭包

Julia作用域细节:在循环中定义闭包,julia,Julia,我正在学习使用。他举了以下例子: anon = Array{Any}(undef, 2) for i = 1:2 anon[i] = () -> println(i) i += 1 end 现在调用此数组中的两个函数将输出: julia> anon[1](); anon[2]() 2 3 我不明白为什么输出是2,3而不是1,2。在第一次通过循环时,i=1,因此,anon[1]=()->println(1)。作者继续说: 这里,anon[1]和anon[2]都是匿名

我正在学习使用。他举了以下例子:

anon = Array{Any}(undef, 2)
for i = 1:2
    anon[i] = () -> println(i)
    i += 1
end
现在调用此数组中的两个函数将输出:

julia> anon[1](); anon[2]()
2
3
我不明白为什么输出是2,3而不是1,2。在第一次通过循环时,
i=1
,因此,
anon[1]=()->println(1)
。作者继续说:

这里,
anon[1]
anon[2]
都是匿名函数。当他们 使用
anon[1]()
anon[2]()
调用,它们打印
2
3
( i当它们被创建时加上1)


然后使用
let
实现预期的行为。然而,我在这个解释中缺少的是Julia作用域规则是如何运作的,以便产生2,3的第一个(意外)结果。换句话说,值2和3是如何得到的?有人能解释一下吗?谢谢

这相当棘手。你需要知道两件事:

  • for
    循环变量
    i
    中,每次迭代都会有一个新的绑定(我想你知道-我不知道Ivo的书,但从你的问题来看,我想这就是他在里面讨论的内容)
  • 在Julia中,闭包是通过创建一个对象来实现的,该对象从外部作用域捕获变量,然后可以访问它
现在,为了解释第二点,请看以下内容(我假设您已经运行了上面的代码):

您可以看到,
anon[1]
已将循环第一次迭代中出现的
i
绑定装箱。与第二个循环一样,
i
的绑定是新的
anon[2]
引用了这个新绑定

您甚至可以这样访问此内存位置:

julia> anon[1].i.contents = 100
100

julia> anon[1]()
100
甚至像这样(不推荐):


最后请注意,在循环的一次迭代中,将变量
i
赋值不会改变绑定(您正在写入相同的内存位置)。

谢谢您的回答。我猜这里令人困惑的是,函数定义出现在循环中的时间早于
I
的增加时间。我去了R,那里我比较熟悉,但那里的行为却很相似。也就是说,计数的是循环出口处的
i
。此外,迭代器定义中的
i
似乎与函数定义中的
i
不同。第一个是
{Any,1}
类型的数组,而前者是
Int64
。我或多或少是对的吗?在循环
I
中有一个类型为
Int64
并指向内存中某个地址的变量。因此,在语句
anon[i]
中,
println(i)
i+=1
变量
i
在循环的一次迭代中引用了内存中完全相同的地址(尽管存储在该地址中的值会发生变化)。但是,对于每个循环,Julia都会为
i
分配一个新的绑定,因此在每次迭代中
i
都指向不同的内存位置。现在清楚了吗?
julia> anon[1].i.contents = 100
100

julia> anon[1]()
100
julia> for i = 1:2
           anon[i] = () -> println(i)
           anon[i].i.contents = 100 + i
           i += 1
           println(i)
       end
102
103

julia> anon[1]()
102

julia> anon[2]()
103