了解 Julia 如何复制变量

Tyl*_*erD 5 copy julia

我试图理解 Julia 如何复制和处理变量。看一下以下示例和以下问题:

a = 1
b = 1
a === b #why do they share the same address? I defined them independently

a = 1
b = a
a === b #true, this makes sense
a = 10 #b still has value of 1 though? why is that when they share the same address as per above?


a = (1,2)
b = (1,2)
a === b #how can they have the same address?

a = [1,2]
b = [1,2]
a !== b #this is true as I would expect (thanks to phipsgabler)
Run Code Online (Sandbox Code Playgroud)

Ste*_*ski 9

其他答案都没有涉及的事情并不x === y意味着“在内存中x是否y有相同的地址?” 正如你的问题所暗示的。相反,正如文档所述===,该x === y操作的作用是:

\n
\n

确定 和 是否x相同y,从某种意义上说,没有程序可以区分它们。

\n
\n

这就是 \xe2\x80\x9c egal\xe2\x80\x9c 谓词,如亨利·贝克 (Henry Baker) 的著名论文“功能对象的平等权利,或者,事物变化越多,它们就越相同”中所介绍的。本文的见解是,在比较两个对象时,您并不真正关心它们在内存中的位置,这是一个可能会改变的实现细节。相反,您真正想知道的是是否可以编写一些程序来区分它们,这仅取决于语言的语义。然后本文继续探讨这对于可变和不可变数据结构意味着什么。

\n

如果两个对象是不可变的并且具有相同的类型和值,那么您无法区分它们,即使程序内存中的某个位置恰好有该值的两个副本。因此它们是===(即egal)。1正如您的示例所示,是否源自两个不同的作业并不重要。您的直觉似乎是,当有人编写时,a = 1每次都会创建b = 1一个新对象。但同样可以有一个全局共享对象,文字语法会获取对其的引用。事实上,情况是这样的:小整数值是静态分配的,并且每当出现小整数文字时都会使用相同的实例。这在其他语言中也是如此,例如 Python:1111

\n
# Python\n>>> a = 1\n>>> b = 1\n>>> a is b\nTrue\n
Run Code Online (Sandbox Code Playgroud)\n

如果语言经常需要 \xe2\x80\x9cboxed\xe2\x80\x9d 整数值实例,则会执行此操作,因为一遍又一遍地分配许多装箱副本的效率很低。相反,它\xe2\x80\x99s标准具有小盒装整数的静态\xe2\x80\x9ccache\xe2\x80\x9d。但是,整数太多,无法全部缓存,因此对于足够大的整数,不会发生这种情况:

\n
# Python\n>>> a = 1234\n>>> b = 1234\n>>> a is b\nFalse\n
Run Code Online (Sandbox Code Playgroud)\n

我正在使用的 Python 版本 (3.9) 的截止值恰好是 256:

\n
# Python\n>>> a = 256\n>>> b = 256\n>>> a is b\nTrue\n>>> a = 257\n>>> b = 257\n>>> a is b\nFalse\n
Run Code Online (Sandbox Code Playgroud)\n

但当然,如果他们决定扩大或缩小缓存,这可能会改变,这有点不幸。即使它永远不会改变,它\xe2\x80\x99s 绝对是实现细节的泄漏。关于缓存小整数的语言的这一点可以向您表明,即使我们===像 Python 那样基于地址1 === 1仍然是正确的。它不是基于地址,但是,它比较任何大小的整数的类型和值:

\n
julia> a = 9223372036854775807; # typemax(Int)\n\njulia> b = 9223372036854775807; # typemax(Int)\n\njulia> a === b\ntrue\n
Run Code Online (Sandbox Code Playgroud)\n

为什么不按地址比较整数?我们不想强迫不可变对象拥有明确定义的地址概念。在优化良好的程序中,整数值通常根本不会存储在内存中。它可能只存在于寄存器中,或者可能被完全消除。如果在多个地方使用相同的常量值,您可能希望避免浪费多个寄存器来存储它,而只需将其保存在您多次使用的单个寄存器中。如果编译器必须跟踪整数的分配语义,那么它将禁止各种优化。

\n

接下来让我们看看你的最后一个例子:

\n
julia> a = [1,2]\n2-element Vector{Int64}:\n 1\n 2\n\njulia> b = [1,2]\n2-element Vector{Int64}:\n 1\n 2\n\njulia> a !== b\ntrue\n
Run Code Online (Sandbox Code Playgroud)\n

为什么相同的数组文字语法会生成这样的数组!==?为什么我们上面用于整数的逻辑不适用?首先,让我们证明您可以编写一个与以下内容不同的a程序b

\n
julia> a[1] = -1\n-1\n\njulia> a == b\nfalse\n
Run Code Online (Sandbox Code Playgroud)\n

我们变异a和不变异b之后,它们不再具有相同的内容,因此它们很容易区分。因此他们不可能是===。为什么我们不能对整数做同样的事情?因为它们是不可变的,所以您无法更改它们的内容。这不仅仅是可变数组的一些偶然特征,而是它们的定义特征:它们是具有独立于其内容的身份的容器,因此必须与内存中的某个位置相关联,以便当其中一部分被调用时它们可以一致地表现。程序更改其内容,程序的其他部分会观察到该更改。

\n

中间的元组示例怎么样?与数组不同,元组是不可变的\xe2\x80\x94之类的整数,它们它们的值,除了它们包含的内容之外,它们没有任何标识。因此,(1, 2) === (1, 2)不关心是否有一个或两个元组文字:它们是无法区分的,只能通过它们的内容来识别,在这种情况下,它们是相同的。这是一个细微的变化:

\n
julia> a = (1, [2])\n(1, [2])\n\njulia> b = (1, [2])\n(1, [2])\n\njulia> a === b\nfalse\n
Run Code Online (Sandbox Code Playgroud)\n

为什么这些都不是===?它们是具有相同内容的不可变元组?那么,“相同”是什么意思呢?正确的定义是===递归地应用于内容,并且由于两个数组不是同一个数组,即使它们当前都保存 value 2,因此这些元组不是递归地无法区分的。或者更简洁地说:

\n
julia> a[2][1] = 0\n0\n\njulia> a\n(1, [0])\n\njulia> b\n(1, [2])\n\njulia> a == b\nfalse\n
Run Code Online (Sandbox Code Playgroud)\n

它们不是===因为我们可以访问每个元组中的数组并更改其内容。这是最后一个例子:

\n
julia> v = [2];\n\njulia> a = (1, v)\n(1, [2])\n\njulia> b = (1, v)\n(1, [2])\n\njulia> a === b\ntrue\n
Run Code Online (Sandbox Code Playgroud)\n

现在,两个单独构造的包含可变数组的元组相同的!那是因为它们都包含相同的可变向量作为它们的第二个组件。换句话说:

\n
    \n
  • 元组是===如果它们的组件都是递归的===
  • \n
  • 1因为整数是不可变1===并且它们具有相同的类型和值
  • \n
  • v因为它们是相同v的向量===
  • \n
\n

希望事情能够澄清。

\n


phi*_*ler 5

这与变量无关,更多的是与值的存储位置有关。

引用文档

具有不可变类型的对象可以由编译器自由复制,因为它的不可变性使得无法以编程方式区分原始对象和副本

特别是,这意味着足够小的不可变值(例如整数和浮点数)通常会传递给寄存器(或分配的堆栈)中的函数。

另一方面,可变值是堆分配的,并作为指向堆分配值的指针传递给函数,除非编译器确定没有办法告诉这不是正在发生的情况。

特别是,整数元组是不可变的。这是一个递归确定的属性:

julia> a = (1, [2])
(1, [2])

julia> b = (1, [2])
(1, [2])

julia> a === b
false
Run Code Online (Sandbox Code Playgroud)