问题描述
docs for Slip 提到“Slip 是一个列表,它会自动展平为外部列表(或其他类似列表的容器或可迭代对象)”。根据这个定义,这是完全合理的:
dd my @a = 1,|(2,3); # OUTPUT: «Array @a = [1,2,3]»
然而,我对以下内容感到惊讶:
dd my @b = do {@a[2] := |(3,4); @a} # OUTPUT: «Array @b = [1,slip(3,4)]»
我原以为 slip(3,4)
会变平为 @b,而不是保持 slip
。 (也就是说,很惊讶 @a[2] := |(3,4)
没有与 @a.splice(2,1,[3,4])
相同的语义。)
list assignment 在这里是否被视为一种特殊情况,其语义与普通 Slip 不同?或者是否有关于 Slips/Lists/Arrays 的语义使这一切都保持一致而无需特殊大小写分配?
解决方法
Slip 是一个列表值,它可以展平为外部序列。
所以下面产生了一个扁平化的列表。
1,|(2,3)
这样做是因为逗号 ,
。
如果您将该列表插入到数组中的给定位置,那么您就是将该列表插入到数组中的单个给定位置。
@a[0] = 1,3); # [(1,2,3),]
如果插入 Slip 也会发生同样的事情,因为 Slip 只是 List 的子类。
@a[0] = |(2,3); # [slip(2,]
事实上,Slip 几乎只是一个列表。这是来自 Rakudo 的 Slip 代码。
# A Slip is a kind of List that is immediately incorporated into an iteration
# or another List. Other than that,it's a totally normal List.
my class Slip { # is List
# XXX this makes an empty Slip undefined?
multi method defined (Slip:D: --> Bool:D) { self.Bool }
multi method Slip(Slip:D:) { self }
method CALL-ME (+args) { args.Slip }
multi method raku(Slip:D: --> Str:D) {
nqp::if(
nqp::eqaddr(self,Empty),'Empty',nqp::stmts(
(my str $guts = callsame),nqp::if(
nqp::eqat($guts,'$',0),# we're itemized
nqp::concat('$(slip',nqp::concat(nqp::substr($guts,1),')')),nqp::concat('slip',$guts)
)
)
)
}
multi method List(Slip:D: --> List:D) {
my $list := nqp::create(List);
nqp::bindattr($list,List,'$!todo',nqp::getattr(self,'$!todo'))
if nqp::isconcrete(nqp::getattr(self,'$!todo'));
nqp::bindattr($list,'$!reified','$!reified'))
if nqp::isconcrete(nqp::getattr(self,'$!reified'));
$list
}
}
这只会使 4 个功能起作用。
-
|().defined
这是定义的吗?
如果它包含元素是,否则否。
(Empty
肯定是未定义的,但可能应该定义|(,)
或slip()
。这个方法只是说两者都是未定义的。) -
Slip((1,2))
将现有列表强制转换为 Slip。 -
Empty.raku
/|(,).raku
打印值以便它可能被评估。Empty
是空 Slip 的特定实例,在运行时有一些特殊处理。 -
|().List
获取列表而不是单据。
这需要在这里,因为 Slip 是 List 的子类,所以通常它只会返回自身。
这些都与将 Slip 扁平化为 List 无关。
请注意,即使顶部的注释也表明它只是一个普通列表。
如果您使用列表(ish,在本例中为 List
或 Range
)作为索引,您可以将其展平。
@a[2,] = |(3,4); # [Any,Any,3]
@a[2,3] = |(3,3,4]
@a[2..3] = |(3,4]
通过使用列表索引,您告诉 Raku @a[…]
操作的结果是一个列表,而不是单个值。
这与右值是 Slip 无关。它与右值是 List 的子类有关。
更明确一点。
my $l-value := @a[2]; # contains the actual Scalar object in the array
my $r-value := |(3,4);
$l-value = $r-value;
这与您的代码所做的基本相同
@a[2] = |(3,4);
@a[2] =
是两个独立的操作。索引,然后赋值
@a[2]
返回标量容器,然后 =
将右侧的值分配到该单个容器中。
为了展平 Slip,赋值需要访问 Array 本身。它没有的东西。它只能访问单一的标量容器。所以它将 Slip 插入到它确实拥有的标量中。 (实际上,赋值并不真正知道标量甚至是数组中的成员。)
当你绑定时,你会做一些稍微不同的事情。问题是绑定应该低于常规分配的级别。所以更有可能只是插入一个点而不是展平。
为了做任何不同的事情,@a[2]
必须返回一个知道如何将 Slip 展平为数组的代理。
如果您真的想这样做,请使用 splice
,因为它具有对数组的引用。
my @a = 1,3);
@a.splice: 2,1,|(3,4);
同样,由于 Slip,这并不特别。
,append 和 push 都可以很好地使用滑动:
my @a = 1,3); #[1,3]
@a.append: |(3,4); #[1 2 3 3 4]
@a.push: |(3,4); #[1 2 3 3 4]
相反,没有滑动:
@a.append: (3,4); #[1 2 3 3 4]
@a.push: (3,4); #[1 2 3 (3 4)]