我随便翻了一下Julia的文档
“变量”章节,第一行
变量
Julia 语言中,变量是与某个值相关联(或绑定)的名字。你可以用它来保存一个值(例如某些计算得到的结果),供之后的代码使用。
“与其它语言的显著差异”章节,MATLAB段
Julia 的数组在赋值给另一个变量时不发生复制。执行
A = B
后,改变B
中元素也会修改A
。
Julia 的值在向函数传递时不发生复制。如果某个函数修改了数组,这一修改对调用者是可见的。
我随便翻了一下Julia的文档
“变量”章节,第一行
变量
Julia 语言中,变量是与某个值相关联(或绑定)的名字。你可以用它来保存一个值(例如某些计算得到的结果),供之后的代码使用。
“与其它语言的显著差异”章节,MATLAB段
Julia 的数组在赋值给另一个变量时不发生复制。执行
A = B
后,改变B
中元素也会修改A
。
Julia 的值在向函数传递时不发生复制。如果某个函数修改了数组,这一修改对调用者是可见的。
要不你试试Rust,一个Vec根本没法有两个名字,是否符合你的要求(手动狗头)
让你的Rust代码能编译过可是一门技术活
你这句讲的最明白
我觉得这个设计可能和实际工程实现有关吧。
大部分时候我们其实并不需要额外复制一份数组,所以在C里面往往都是通过传指针或引用来共享数据。在Julia里面默认做的就是类似的传引用的工作,而在确实需要共享数据的情况下,则明确地通过copy
来实现。
a=b=[0]; a===b # true
a=b=0; a===b # true
a=0; b=0; a===b # true
前面三个一样
a=[0]; b=[0]; a===b # false
确定 x 和 y 是否相同,因为没有任何程序可以区分它们。首先比较 x 和 y 的类型。如果这些是
相同的可变对象按内存中的地址进行比较,不可变对象(如数字)按位级别的内容进行比较。这
功能是某个时候
这是我从REPL中找到的关于===的文档,注意内存地址比较
用 ==
不好吗?
a=b=[0]; a==b # true
a=b=0; a==b # true
a=0; b=0; a==b # true
a=[0]; b=[0]; a==b # true
一个求相等,一个求相同
上面回复也说了,文档里关于 ===
的叙述如下
===(x,y) -> Bool ≡(x,y) -> Bool
Determine whether
x
andy
are identical, in the sense that no
program could distinguish them. First the types ofx
andy
are
compared. If those are identical, mutable objects are compared by
address in memory and immutable objects (such as numbers) are compared
by contents at the bit level.
x == y
先比较 x
跟 y
的类型;如果类型一样,则:
所以:
a = [0]; b = [0]; a === b
返回 false
,因为 a
跟 b
是 mutable 变量,不是同一个对象,自然地址不同。
a = 0; b = 0; a === b
返回 true
,因为 a
跟 b
是 Immutable 变量。但是这种情况也不说明 a
跟 b
在内存中地址一样,只能确定内容一样。
至于 a = b = [0]
这种连等的写法,编译器的解读是 a = (b = [0])
;所以 a
跟 b
指向同一数组没问题。
楼上说了,不用赘述(而且你还有一个小错)。
就是啊,感觉都赘述这么久了,所以看不懂你这究竟是哪里有问题……
你又拷贝一段英文出来想说明啥
还请您不吝赐教?
不懂你的用意???
[0]
是堆上的数组,a=[0]; b=[0];
相当于 a = malloc(); b = malloc()
; a === b
比较地址,当然不同。
(0,)
这个才相当于是栈上的数组:
julia> a = (0,)
(0,)
julia> b = (0,)
(0,)
julia> a === b
true
我觉得题主是想说给一个可变的数组起两个名字没有什么意义吧,因为实践中用不到一个东西两个不同的名字。
但是第一,你用不到不代表别人用不到,有些情况下确实别名这个东西可以用做缩写。第二,正如前面的人所说的,除了Rust和Haskell实现了线性类型系统可以满足你这种没有别名的需求以外,其他的语言应该是不符合你的要求的。。。所以你到底在大惊小怪什么。。。
也确实有部分语言能把数组等部分内置类型做成值传递的(特殊处理),不过嘛,这样子内置数组就很特别了,这样的类型系统是不能扩展的。
一般来说动态类型的语言都采用引用模型(或者,如上面所说,也有一些语言把数组特殊处理),不管怎么样,从引用模型得到值模型更加容易一些,因为我们总是可以用copy确保传值。但是从值模型到引用模型要困难一些,因为如果默认传值,我们就需要一个构造来提取对某个值的引用(然后把这个引用作为值传递),这意味着引用本身也要有自己的类型(例如Julia中有Ref
),考虑到科学计算往往我们都是把一个向量,数组传进一个函数里面修改一下(例如说把一个数组清零)得到一个新的答案,所以要求程序员手动构造Ref太麻烦。(一般来说函数的传递和赋值的语义是一致的,因为函数可以视为一个形式参数用实际参数赋值的let语句)
你的这个说法是不正确的,在Julia里只有给值取名(Julia中只有引用语义没有值语义),没有给变量赋值。所以到底要讲清楚什么。。。明明都是引用,没有一点歧义。
真正的问题在于可变与不可变,这个和是不是值还是引用没有关系,所以我就不说了。。。不可变+引用可以相等于值传递(反正你也改不了,所以怎么传递也无所谓)。
而且我觉得值传递是一个ill-defined的东西,很多东西都没有自然定义的值传递,大多数情况下,值传递都是指浅层拷贝(数组拷贝第一层?如果是数组的数组呢?),但是如果用一个树来赋值,难道我们不应该深层拷贝吗?所以说这个东西牵扯到很多很复杂的东西,很多动态语言设计的时候,直接就全部引用传递了(或者也可以向Haskell学习,变成纯函数的,这样就可以等价于全部值引用了)
另外你举的这个例子已经被 Gnimuc说清楚了,x=y=[0]
是x=[0];y=x
的简写,就相当于只调用了malloc
一次,x=[0];y=[0]
构造了数组两次,所以是两个不同的数组。
如果你觉得这个语法有歧义的话(你怕出错的话),那么你就不应该使用这个语法。我看这个回答下很多人貌似都没觉得这到底有什么问题(虽然他们的回答也不完全严谨就是了),我觉得你不能纯粹把别的语言的东西搬过来,然后语法上一样就觉得是一样的东西。按照你这个道理,C语言里的void
和void*
还有歧义,因为void*不是指向void的指针。。。
我再总结一下这个问题:
这个问题本质上是关于变量和赋值语义的互相作用。
Julia的变量其实是对值的引用,例如说:
x=1
y=[1,2,3]
x是一个对整数的引用,而y是一个对数组的引用
当对一个表达式求值的时候,每个(右值)变量被解引用得到其值(所以x解引用得到1,y解引用得到一个数组,但是数组其实是用一个指向第一个元素的指针表示的,所以y的值其实是一个int*指针),这都好理解
问题出在赋值上,Julia的赋值就是一个变量重绑定的过程(更换引用)
所以
x=1
y=2
y=x+3
y先是一个对2的引用,然后变成对5的引用。同理:
x = [0]
y = x
x是一个对int的引用。y赋值后也得到这个对int的引用,所以是指向同一个东西。
所以我们应该把这个过程分为两步:变量总是引用的,赋值总是引用重绑定的(因为例如数组传的其实是数组的指针。。。一个变量引用一个指针,所以一个变量其实是一个二级指针int**),不管可变不变。Julia中只有这个语义。
真正tricky的地方在于,我们会认为[1,2,3]作为数组的值。应该是这个数组的整体(整个[1,2,3]),而非指向这个数组指针。所以我们会认为
x=[1,2,3]
y=x
如果是数组整个作为值进行值传递的话,那么就应该拷贝一份数组。但是我们已经看到了,Julia的变量总是引用,而赋值是重绑定的过程。这和怎么传递根本就没有关系,语言选择了这个语义,就必然要导致这个结果。
为什么一些别的语言需要这个值传递还是引用传递的区分?以C为例,这是因为C可以对一个变量取引用,注意在严格求值的语言中,仅仅用取引用函数是不能做到这一点的!
考虑两个程序:
#julia
x = 0
y = Ref(x)
y[]+=1
#C
int x = 0
int* y = &x
*y += 1
区别在于Julia的程序中,尽管我们写着Ref(x)
但是我们没法得到对于x所指向0的指针(因为Ref(x)的x先被求值为0,所以Ref(0)重新构造了指向另外一个0的指针),从而我们不能改动对于x的值。
所以在一些(静态)语言中,赋值才要做出这种区分,因为可以对变量取指针,因此可以随便改动这个变量所指内存区域的值,迫使我们要区分赋值是产生别名还是产生拷贝(要不然的话我们可能会不小心把本来不同的变量弄成是别名的)。但因为Julia中你反正也不能这么做,所以区分到底怎么传递也毫无意义。