When we slice an array with an index that exceeds the boundaries of the array we get as the result the undefined (Any)
When we pass the same slice index as a lazy list then we get as result the existing values of the array/list (and NOT any more than that):
my @a = ^5;
say @a[^10]; # (0 1 2 3 4 (Any) (Any) (Any) (Any) (Any))
say @a[lazy ^10]; # (0 1 2 3 4)
It is clear that lazyness of the slice index affects the result.
Trying to undestand the way things are and as a proof of concept I programmed my simple version of the slice mechanism:
my @a = ^5;
my @s1 = ^10;
my @s2 = lazy ^10;
sub postcircumfix:<-[ ]-> (@container, @index) {
my $iter = @index.iterator;
gather {
loop {
my $item := $iter.pull-one;
if $item =:= IterationEnd {
last;
}
with @container[$item] {
take @container[$item]
} else {
@index.is-lazy ?? { last } !! take @container[$item];
}
}
}
}
say @a-[@s1]-; # (0 1 2 3 4 (Any) (Any) (Any) (Any) (Any))
say @a-[@s2]-; # (0 1 2 3 4)
But I am wondering if my naive algorithm depicts the way that things are computed under the hood !
However, slicing arrays are different from slicing lists so that the users can slice in multiple dimensions in arrays. Moreover, the list slicing returns a new array, but the slice in the array returns the view of the same array.
Slicing a python array means getting items from a particular index to another particular index—for instance, printing elements from index two to seven from an array containing ten elements. The users can similarly slice the python lists.
There are the following three ways to find a slice of an array: Let's discuss each method in detail. It is a native method for getting a slice of an array. In this method, first, we find the start and end index of the given array. After that, we create an empty array (sliced array) of size (endIndex - startIndex).
As we can see for both the cases, start and step are set by default to 0 and 1. The sliced arrays contain elements of indices 0 to (stop-1). This is one of the quickest methods of array slicing in Python.
The source for how things are done under the hood can be found in array_slice.pm6.
Specifically, you can see the following at L73:
if is-pos-lazy {
# With lazy indices, we truncate at the first one that fails to exists.
my \rest-seq = Seq.new(pos-iter).flatmap: -> Int() $i {
nqp::unless(
$eagerize($i),
last,
$i
)
};
my \todo := nqp::create(List::Reifier);
nqp::bindattr(todo, List::Reifier, '$!reified', eager-indices);
nqp::bindattr(todo, List::Reifier, '$!current-iter', rest-seq.iterator);
nqp::bindattr(todo, List::Reifier, '$!reification-target', eager-indices);
nqp::bindattr(pos-list, List, '$!todo', todo);
}
else {
pos-iter.push-all: target;
}
So, as you've surmised, it does indeed stop after a list item doesn't exist. This is no doubt becaue many lazy lists are infinite, and iterators don't provide a way to know if they are infinite or not (the generator may be non-determinative).
If you really want to enable such a thing, you could, for instance, write your own slicer that handles lazy lists where an element may not be available, but you have to take care to ensure that things are only eagerly evaluated if you know they're finite:
multi sub postcircumfix:<-[ ]-> (@a, @b) {
lazy gather {
take @a[$_] for @b;
}
}
my @a = ^5;
my @b = lazy gather {
for ^10 -> $i {
# So we can track when elements are evaluated
say "Generated \@b[$i]";
take $i;
}
};
say "Are we lazy? ", @a-[@b]-;
say "Let's get eager: ", @a-[@b]-.eager;
say "Going beyond indices: ", @a-[@b]-[11]
The output of this is
Are we lazy? (...)
Generated @b[0]
Generated @b[1]
Generated @b[2]
Generated @b[3]
Generated @b[4]
Generated @b[5]
Generated @b[6]
Generated @b[7]
Generated @b[8]
Generated @b[9]
Let's get eager: (0 1 2 3 4 (Any) (Any) (Any) (Any) (Any))
Going beyond indices: Nil
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