Echo nil > me

Bleach me and begin again

What's the Result of N << N?

| Comments

昨天在ruby-china上看到关于ruby的一个很有意思的问题:

1
2
3
4
5
6
n = [1, 2]
a = n << n
p a
=> [1, 2, [...]]
a[2]
=> [1, 2, [...]]

这是一个无限嵌套的数组,数组允许嵌套,这很正常,不过为什么会出现无限嵌套呢? 好多人给出了各式的答案,还提到了用direct graph去表示递归数组的stackoverflow的一个问题 就是用有向图去表示了一个<<的过程,不过感觉还是没解释这个的原因。

<<

先从<<符号说起,这是一个ruby上用的很多的符号,大概表示append或push的意思,可以用于String,Array等等的类型,甚至是非这些基础类,比如rails里的1-n的关系中就有用到。

object_id

在解释之前要先说一下object_id,这个有点想C++或java里的对象的地址,简单来说就是唯一标识对象的。 在irb里,可以看到[1, 2] << [1, 2]这个的结果是和我们预期的一样的:[1, 2, [1, 2]],而查看[1, 2]object_id,会发现

1
2
3
4
5
6
1.9.3p194 :005 > [1, 2].object_id
 => 84087500 
1.9.3p194 :006 > [1, 2].object_id
 => 84084000 
1.9.3p194 :007 > [1, 2].object_id
 => 84104730 

也就是说,看似一样的前后的[1, 2]其实并不一样,也就是说两个不同的对象(object_id),比如aba << b所得到的结果是正常的。 而n << n的结果为什么有点出乎意料呢?

My explanation

在我看来这应该只是Matz做的一个约定或他对于这种情况——<<作用于自身——的解释和理解吧。 而我的解释是这样的,在<<右边的n作用于左边的n时,左边的n就发生了变化,变成了[1, 2, [1, 2]],到这里还是很正常的。

但是,问题来了,加入把这个过程放慢的话,左边的n发生了变化,右边的n也要发生变化啊,所以右边的n就也变成了[1, 2, [1, 2]]。 但还没完,右边的n正在作用于左边的n,所以左边的n其实就又成了[1, 2, [1, 2, [1, 2]]].

如此下去,是没完没了了,所以就变成了无限的嵌套数组,无限的递归下去了。这里应该也可以报错的吧,不过Matz还是选择了用[...]来代表无限的递归嵌套。

Hash也是可以有这样的结果的(来自@hooopo):

1
2
3
4
1.9.3p194 :032 > a = {:a => 1}
 => {:a=>1} 
1.9.3p194 :033 > a[:b] =  a
 => {:a=>1, :b=>{...}} 

应该是因为Array和Hash允许嵌套的形式,所以会出现这种情况,换种类型就不一定了,比如String:

1
2
3
4
1.9.3p194 :023 > n = "abc"
 => "abc" 
1.9.3p194 :024 > n << n
=> "abcabc"

当然,这仅仅是个人对于ruby里这样的结果的一个思考和猜想,至于真正原因,恐怕还要问Matz

Comments