The docs for Slip mention that "A Slip is a List that automatically flattens into an outer List (or other list-like container or iterable)". Based on this definition, this makes perfect sense:
dd my @a = 1, |(2, 3); # OUTPUT: «Array @a = [1, 2, 3]»
However, I am suprised by the following:
dd my @b = do {@a[2] := |(3, 4); @a} # OUTPUT: «Array @b = [1, 2, slip(3, 4)]»
I would have expected the slip(3, 4)
to flatten out into @b, rather than to remain a slip
. (That is, was surprised that @a[2] := |(3, 4)
didn't have the same semantics as @a.splice(2, 1, [3, 4])
.)
Is list assignment getting treated as a special case here, with different semantics than normal Slips? Or is there something about the semantics of Slips/Lists/Arrays that makes this all consistent without special-casing assignment?
A Slip is a list value that can flatten into an outer sequence.
So the following produces a flattened list.
1, |(2, 3)
It does so because of the comma ,
.
If you insert that list into an array at a given position, you are inserting that list into the array at a single given position.
@a[0] = 1, |(2, 3); # [(1,2,3),]
The same thing happens if you insert a Slip, since a Slip is just a subclass of List.
@a[0] = |(2, 3); # [slip(2,3),]
In fact a Slip is almost exclusively just a list. Here is the code from Rakudo for a 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,List,'$!todo'))
if nqp::isconcrete(nqp::getattr(self,List,'$!todo'));
nqp::bindattr($list,List,'$!reified',nqp::getattr(self,List,'$!reified'))
if nqp::isconcrete(nqp::getattr(self,List,'$!reified'));
$list
}
}
That only makes 4 features work.
|().defined
Is this defined?Empty
is definitely undefined, but |(,)
or slip()
maybe should be defined. This method just says that both are undefined.)Slip((1,2))
Coerce an existing list into a Slip.Empty.raku
/ |(,).raku
Print the value such that it could potentially be evalled.Empty
is a specific instance of an empty Slip, that has some special handling in the runtime.|().List
Get a List instead of a Slip.None of that has anything to do with a Slip flattening into a List.
Note that even the comment at the top states that it is just a normal List.
You can get it to flatten it if you use a list (ish, in this case a List
or a Range
) as the index.
@a[2, ] = |(3,4); # [Any, Any, 3]
@a[2, 3] = |(3,4); # [Any, Any, 3, 4]
@a[2..3] = |(3,4); # [Any, Any, 3, 4]
By using a list index, you are telling Raku that the result of the @a[…]
operation is a list, instead of a single value.
That has nothing to do with the rvalue being a Slip. It has everything to do with the rvalue being a subclass of List.
A bit more explicit.
my $l-value := @a[2]; # contains the actual Scalar object in the array
my $r-value := |(3,4);
$l-value = $r-value;
That is basically the same as what your code does
@a[2] = |(3,4);
The @a[2] =
is two separate operations. The indexing, and then the assignment
The @a[2]
returns the Scalar container, then the =
assigns the value on the right into that singular container.
For it to flatten the Slip, the assignment would need access to the Array itself. Something it doesn't have. It only has access to the singular Scalar container. So it inserts the Slip into the Scalar it does have. (In fact the assignment doesn't really know that the Scalar is even a member in an Array.)
When you bind you do something slightly different. The thing is that binding is supposed to be lower level than a regular assignment. So it would be even more likely to just insert into a single spot instead of flattening.
For it to do anything different, the @a[2]
would have to return a Proxy that knows how to flatten a Slip into an array.
If you really want to do that use splice
as it has a reference to the array.
my @a = 1, |(2, 3);
@a.splice: 2, 1, |(3,4);
Again that isn't special because of the Slip.
append and push both play nice with slip:
my @a = 1, |(2, 3); #[1,2,3]
@a.append: |(3,4); #[1 2 3 3 4]
@a.push: |(3,4); #[1 2 3 3 4]
in contrast, without the slip:
@a.append: (3,4); #[1 2 3 3 4]
@a.push: (3,4); #[1 2 3 (3 4)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With