Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What counts as an "outer list" for a Slip?

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?

like image 498
codesections Avatar asked Jun 06 '21 02:06

codesections


Video Answer


2 Answers

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.

  1. |().defined Is this defined?
    If it contains elements yes, otherwise no.
    (Empty is definitely undefined, but |(,) or slip() maybe should be defined. This method just says that both are undefined.)
  2. Slip((1,2)) Coerce an existing list into a Slip.
  3. 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.
  4. |().List Get a List instead of a Slip.
    This needs to be here because a Slip is a subclass of List, so normally it would just return itself.

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.

like image 164
Brad Gilbert Avatar answered Sep 19 '22 23:09

Brad Gilbert


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)]
like image 29
p6steve Avatar answered Sep 23 '22 23:09

p6steve