Author: larry Date: Wed Mar 28 19:28:28 2007 New Revision: 14359 Modified: doc/trunk/design/syn/S09.pod
Log: User-definable array indexing hammered out by TheDamian++ and Dataweaver++ Modified: doc/trunk/design/syn/S09.pod ============================================================================== --- doc/trunk/design/syn/S09.pod (original) +++ doc/trunk/design/syn/S09.pod Wed Mar 28 19:28:28 2007 @@ -12,9 +12,9 @@ Maintainer: Larry Wall <[EMAIL PROTECTED]> Date: 13 Sep 2004 - Last Modified: 14 Mar 2007 + Last Modified: 28 Mar 2007 Number: 9 - Version: 18 + Version: 19 =head1 Overview @@ -146,6 +146,87 @@ buffer type. The unpacking is performed by coercion of such a buffer type back to the type of the compact struct. +=head1 Standard array indexing + +Standard array indices are specified using square brackets. Standard +indices always start at zero in each dimension of the array (see +L<"Multidimensional arrays">), and are always contiguous: + + @dwarves[0] = "Happy"; # The 1st dwarf + @dwarves[6] = "Doc"; # The 7th dwarf + + @seasons[0] = "Spring"; # The 1st season + @seasons[2] = "Autumn"|"Fall"; # The 3rd season + + +=head1 Fixed-size arrays + +A basic array declaration like: + + my @array; + +declares a one-dimensional array of indeterminate length. Such arrays +are autoextending. For many purposes, though, it's useful to define +array types of a particular size and shape that, instead of +autoextending, fail if you try to access outside their +declared dimensionality. Such arrays tend to be faster to allocate and +access as well. (The language must, however, continue to protect you +against overflow--these days, that's not just a reliability issue, but +also a security issue.) + +To declare an array of fixed size, specify its maximum number of elements +in square brackets immediately after its name: + + my @dwarves[7]; # Valid indices are 0..6 + + my @seasons[4]; # Valid indices are 0..4 + +No intervening whitespace is permitted between the name and the size +specification, but "unspace" is allowed: + + my @values[10]; # Okay + my @keys [10]; # Error + my @keys\ [10]; # Okay + +Note that the square brackets are a compile-time declarator, not a run-time +operator, so you can't use the "dotted" form either: + + my @values.[10]; # Error + my @keys\ .[10]; # Error + +Attempting to access an index outside a array's defined range will fail: + + @dwarves[7] = 'Sneaky'; # Fails with "invalid index" exception + +It's also possible to explicitly specify a normal autoextending array: + + my @vices[*]; # Length is: "whatever" + # Valid indices are 0..* + +=head1 Typed arrays + +The type of value stored in each element of the array (normally C<Any>) +can be explicitly specified too, as an external C<of> type: + + my num @nums; # Each element stores a native number + my @nums of num; # Same + + my Book @library[1_000_000]; # Each element stores a Book object + my @library[1_000_000] of Book; # Same + +Alternatively, the element storage type may be specified as part of the +dimension specifier (much like a subroutine definition): + + my @nums[-->num]; + + my @library[1_000_000 --> Book]; + +Arrays may also be defined with a mixture of fixed and autoextending +dimensions: + + my @calendar[12;*;24]; # "Month" dimension unlimited + + =head1 Compact arrays In declarations of the form: @@ -166,6 +247,10 @@ hard to make these elements look like objects when you treat them like objects--this is called autoboxing.) +Such arrays are autoextending just like ordinary Perl arrays +(at the price of occasionally copying the block of data to another +memory location, or using a tree structure). + A compact array is for most purposes interchangeable with the corresponding buffer type. For example, apart from the sigil, these are equivalent declarations: @@ -204,33 +289,45 @@ known encoding. Otherwise you must encode them explicitly from the higher-level abstraction into some buffer type.) + =head1 Multidimensional arrays -The declarations above declare one-dimensional arrays of indeterminate -length. Such arrays are autoextending just like ordinary Perl arrays -(at the price of occasionally copying the block of data to another -memory location, or using a tree structure). For many purposes, -though, it's useful to define array types of a particular size and -shape that, instead of autoextending, throw an exception if you try -to access outside their declared dimensionality. Such arrays tend -to be faster to allocate and access as well. (The language must, -however, continue to protect you against overflow--these days, that's -not just a reliability issue, but also a security issue.) +Perl 6 arrays are not restricted to being one-dimensional (that's simply +the default). To declare a multidimensional array, you specify it with a +semicolon-separated list of dimension lengths: + + my int @ints[4;2]; # Valid indices are 0..3 ; 0..1 + + my @calendar[12;31;24]; # Valid indices are 0..11 ; 0..30 ; 0..23 + +You can pass a multislice for the shape as well: + + @@shape = (4;2); + my int @ints[ [;[EMAIL PROTECTED] ]; + my int @ints[@@shape]; # Same thing + +Again, the C<[;]> list operator interpolates a list into a semicolon +list. + +The shape may be supplied entirely by the object at run-time: + + my num @nums = Array of num.new(:shape(3;3;3)); + my num @nums .=new():shape(3;3;3); # same thing A multidimensional array is indexed by a semicolon list, which is really -a list of feeds in disguise. Each sublist is a slice/feed of one particular -dimension. So +a list of feeds in disguise. Each sublist is a slice/feed of one +particular dimension. So: @array[0..10; 42; @x] -is really short for +is really short for: @array.postcircumfix:<[ ]>( <== 0..10 <== 42 <== @x ); The compiler is free to optimize to something faster when it is known that lazy multidimensional subscripts are not necessary. -Note that +Note that: @[EMAIL PROTECTED],@y] @@ -261,122 +358,359 @@ distinct dimensions: my @@x; - @@x <== %hash.keys.grep: {/^X/}; + @@x <== %hash.keys.grep: {/^\d+$/}; @@x <== =<>; @@x <== 1..*; @@x <== gather { loop { take rand 100 } }; - %hash{@@x} + @array{@@x} -Conjecture, since @@x and @x are really the same object, any array can +Conjecture: since @@x and @x are really the same object, any array can keep track of its dimensionality, and it only matters how you use it in contexts that care about the dimensionality: my @x; - @x <== %hash.keys.grep: {/^X/}; + @x <== %hash.keys.grep: {/^\d+$/}; @x <== =<>; @x <== 1..*; @x <== gather { loop { take rand 100 } }; - %hash{@@x} # multidimensional - [EMAIL PROTECTED] # flattened + @array{@@x} # multidimensional + @[EMAIL PROTECTED] # flattened -To declare a multidimensional array, you may declare it with a signature as -if it were a function returning I<one> of its entries: +=head2 Autoextending multidimensional arrays - my num @nums (Int); # one dimension, @nums[Int] +Any dimension of the array may be specified as "C<*>", in which case +that dimension will autoextend. Typically this would be used in the +final dimension to make a ragged array functionally equivalent to an +array of arrays: -or alternately: + my int @ints[42; *]; # Second dimension unlimited + push(@ints[41], getsomeints()); - my @nums (Int --> num); # one dimension, @nums[Int] +but I<any> dimensional of an array may be declared as autoextending: -You can use ranges as types: + my @calendar[12;*;24]; # "Month" dimension unlimited + @calendar[1;42;8] = 'meeting' # See you on January 42nd - my @nums (0..2 --> num); # one dimension, @nums[0..2] - my @ints (0..3, 0..1 --> int); # one dimension, @ints[0..3; 0..1] +It is also possible to specify that an array has an arbitrary number +of dimensions, using a "hyperwhatever" (C<**>) at the end of the +dimensional specification: -That includes the "upto" range type: + my @grid[**]; # Any number of dimensions + my @spacetime[*;*;*;**]; # Three or more dimensions + my @coordinates[100;100;100;**]; # Three or more dimensions - my @ints (^4, ^2 --> int); # one dimension, @ints[0..3; 0..1] +Note that C<**> is a shorthand for C<[;] * xx *>, so the extra +dimensions are all of arbitrary size. To specify an arbitrary number +of fixed-size dimensions, write: -You can pretend you're programming in Fortran, or awk: + my @coordinates[ [;] 100 xx * ]; - my int @ints (1..4, 1..2); # two dimensions, @ints[1..4; 1..2] +This syntax is also convenient if you need to define a large number of +consistently sized dimensions: -Note that this only influences your view of the array in the current -lexical scope, not the actual shape of the array. If you pass -this array to another module, it will see it as having a shape -of C<(0..3,0..1)> unless it also declares a variable to view it -differently. + my @string_theory[ [;] 100 xx 11 ]; # 11-dimensional -Alternately, you may declare it using a prototype subscript, -but then you must remember to use semicolons instead of commas to -separate dimensions, because each slice represents an enumeration of -the possible values, so the following are all equivalent: +=head1 User-defined array indexing - my @ints (0..3, 0..1 --> int); - my int @ints (0..3, 0..1); - my int @ints[^4;^2]; - my int @ints[0..3; 0..1]; - my int @ints[0,1,2,3; 0,1]; +Any array may also be given a second set of user-defined indices, which +need not be zero-based, monotonic, or even integers. Whereas standard array +indices always start at zero, user-defined indices may start at any +finite value of any enumerable type. Standard indices are always +contiguous, but user-defined indices need only be distinct and in an +enumerable sequence. -You can pass a multislice for the shape as well: +To define a set of user-defined indices, specify an explicit or +enumerable list of the indices of each dimension (or the name of an +enumerable type) in a set of curly braces immediately after the +array name: - @@fooshape = (0..3; 0..1); - my int @ints[[;[EMAIL PROTECTED]; - my int @ints[@@fooshape]; # same thing + my @dwarves{ 1..7 }; + my @seasons{ <Spring Summer Autumn Winter> }; -Again, the C<[;]> list operator interpolates a list into a semicolon -list, which we do for consistency with subscript notation, not because -it makes a great deal of sense to allow slices for dimensional specs -(apart from ranges). So while the following is okay: + my enum Months + «:Jan(1) Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec»; - my int @ints[0,1,2,3,4]; # same as 0..4 + my @calendar{ Months; 1..31; 9..12,14..17 }; # Business hours only -the following is a semantic error that the compiler should catch: +Array look-ups via user-defined indices are likewise specified in curly +braces instead of square brackets: - my int @ints[^3,^3,^3]; # oops, comma instead of semicolon + @dwarves{7} = "Doc"; # The 7th dwarf -The shape may be supplied entirely by the object at run-time: + say @calendar{Jan;13;10}; # Jan 13th, 10am - my num @nums = Array of num.new(:shape(^3;^3;^3)); - my num @nums .=new():shape(^3;^3;^3); # same thing +User-defined indices merely provide a second, non-standard "view" of the +array; the underlying container remains the same. Each user-defined +index in each dimension is mapped one-to-one back to the standard (zero- +based) indices of that dimension. So, given the preceding definitions: -Any dimension of the array may be specified as "C<Int>", in which case -that dimension will autoextend. Typically this would be used in the -final dimension to make a ragged array functionally equivalent to an -array of arrays: + maps to + @dwarves{1} ------> @dwarves[0] + @dwarves{2} ------> @dwarves[1] + : : + @dwarves{7} ------> @dwarves[6] - my int @ints[^42; Int]; - push(@ints[41], getsomeints()); +and: + + maps to + @seasons{'Summer'} ------> @seasons[0] + @seasons{'Spring'} ------> @seasons[1] + @seasons{'Autumn'} ------> @seasons[2] + @seasons{'Winter'} ------> @seasons[3] + + @seasons<Summer> ------> @seasons[0] + @seasons<Spring> ------> @seasons[1] + @seasons<Autumn> ------> @seasons[2] + @seasons<Winter> ------> @seasons[3] + +and: + + maps to + @calendar{Jan;1;9} ------> @calendar[0;0;0] + @calendar{Jan;1;10} ------> @calendar[0;0;1] + : : + @calendar{Jan;1;12} ------> @calendar[0;0;3] + @calendar{Jan;1;14} ------> @calendar[0;0;4] + : : + @calendar{Feb;1;9} ------> @calendar[1;0;0] + : : + @calendar{Dec;31;17} ------> @calendar[11;30;7] + +User-defined indices can be open-ended, but only on the upper end (i.e. +just like standard indices). That is, you can specify: + + my @sins{7..*}; # Indices are: 7, 8, 9, etc. + +but not: + + my @virtue{*..6}; + my @koalas{*..*}; + my @celebs{*}; + +These last three are not allowed because there is no first index, and +hence no way to map the infinity of negative user-defined indices back +to the standard zero-based indexing scheme. + +Declaring a set of user-defined indices implicitly declares the array's +standard indices as well (which are still zero-based in each dimension). +Such arrays can be accessed using either notation. The standard indices +provide an easy way of referring to "ordinal" positions, independent of +user-specified indices: + + say "The first sin was @sins[0]"; + # First element, no matter what @sin's user-defined indexes are + +Note that if an array is defined with fixed indices (either standard or +user-defined), any attempt to use an index that wasn't specified in the +definition will fail. For example: + + my @values{2,3,5,7,11}; # Also has standard indices: 0..4 + + say @values[-1]; # Fails (not a valid standard index) + say @values{1}; # Fails (not a valid user-defined index) + + say @values{4}; # Fails (not a valid user-defined index) + + say @values[5]; # Fails (not a valid standard index) + say @values{13}; # Fails (not a valid user-defined index) -The shape may also be specified by types rather than sizes: +Furthermore, if an array wasn't specified with user-defined indices, +I<any> attempt to index it via C<.{}> will fail: - my int @ints[Even; Odd]; + my @dwarves[7]; # No user-defined indices; -or by both: + say @dwarves{1}; # Fails: can't map .{1} to a standard .[] index - my int @ints[0..100 where Even; 1..99 where Odd]; +When a C<:k>, C<:kv>, or C<:p> adverb is applied to a full array, +the keys returned are always the standard indices. -(presuming C<Even> and C<Odd> are types already constrained to be even or odd). + my @arr{1,3,5,7,9} = <one two three four five>; -The C<Whatever> type will be taken to mean C<Int> within an array -subscript, so you can also write: + say @arr:k; # 0, 1, 2, 3, 4 - my int @ints[^42; *]; +However, you can specify which set of keys are returned: -Saying + say @arr:k[] # 0, 1, 2, 3, 4 + say @arr:k{} # 1, 3, 5, 7, 9 - my int @ints[^42; **]; +When C<:k>, C<:kv>, or C<:p> is applied to an array slice, it returns +the kind of indices that were used to produce the slice, unless the type +of index is explicitly requested: -would give you an array of indeterminate dimensionality. + @arr[0..2]:p # 0=>'one', 1=>'two', 2=>'three' + @arr[0..2]:p[] # 0=>'one', 1=>'two', 2=>'three' + @arr[0..2]:p{} # 1=>'one', 3=>'two', 5=>'three' + + @arr{1,3,5}:p # 1=>'one', 3=>'two', 5=>'three' + @arr{1,3,5}:p[] # 0=>'one', 1=>'two', 2=>'three' + @arr{1,3,5}:p{} # 1=>'one', 3=>'two', 5=>'three' + + +=head1 Inclusive subscripts + +Within any array look-up (whether via C<.[]> or C<.{}>), the "whatever +star" can be used to indicate "all the indices". The meaning of +"all" here depends on the definition of the array. If there are no +pre-specified indices, the star means "all the indices of currently +allocated elements": + + my @data # No pre-specified indices + = 21, 43, 9, 11; # Four elements allocated + say @data[*]; # So same as: say @data[0..3] + + @data[5] = 101; # Now six elements allocated + say @data[*]; # So same as: say @data[0..5] + +If the array is defined with predeclared fixed indices (either standard +or user-defined), the star means "all the defined indices": + + my @results{1..100 :by(2)} # Pre-specified indices + = 42, 86, 99, 1; + + say @results[*]; # Same as: say @results[0..49] + say @results{*}; # Same as: say @results{1..100 :by(2)} + +You can omit unallocated elements, either by using the :v adverb: + + say @results[*]:v; # Same as: say @results[0..3] + say @results{*}:v; # Same as: say @results{1,3,5,7} + +or by using a "zen slice": + + say @results[]; # Same as: say @results[0..3] + say @results{}; # Same as: say @results{1,3,5,7} + +A "whatever star" can also be used as the starting-point of a range +within a slice, in which case it means "from the first index": + + say @calendar[*..5]; # Same as: say @calendar[0..5] + say @calendar{*..Jun}; # Same as: say @calendar{Jan..Jun} + + say @data[*..3]; # Same as: say @data[0..3] + +As the end-point of a range, a lone "whatever" means "to the maximum +specified index" (if fixed indices were defined): + + say @calendar[5..*]; # Same as: say @calendar[5..11] + say @calendar{Jun..*}; # Same as: say @calendar{Jun..Dec} + +or "to the largest allocated index" (if there are no fixed indices): + + say @data[1..*]; # Same as: say @results[1..5] + +=head1 Negative and differential subscripts + +The "whatever star" can also be treated as a number inside a +standard index, in which case it evaluates to the length of the +array. This provides a clean and consistent way to count back or +forwards from the end of an array: + + @array[*-$N] # $N-th element back from end of array + @array[*+$N] # $N-th element at or after end of array + +More specifically: + + @array[*-2] # Second-last element of the array + @array[*-1] # Last element of the array + @array[+*] # First element after the end of the array + @array[*+0] # First element after the end of the array + @array[*+1] # Second element after the end of the array + + @array[*-3..*-1] # Slice from third-last element to last element + +(Note that, if a particular array dimension has fixed indices, any +attempt to index elements after the last defined index will fail.) + +Using a standard index less than zero prepends the corresponding number +of elements to the start of the array and then maps the negative index +back to zero: + + @results[-1] = 42; # Same as: @results.unshift(42) + + @dwarves[-2..-1] # Same as: @dwarves.unshift(<Groovy Sneaky>) + = <Groovy Sneaky>; + +Note that, as with a normal C<unshift>, the new elements are +actually stored starting at standard index zero, after pre-existing +elements have been bumped to the right. Hence after the assignments +in the preceding example: + + say @results[0]; # 42 + say @dwarves[0]; # Groovy + +Using a negative index on an array of fixed size will fail if the +resulting number of elements exceeds the defined size. + +Note that the behaviour of negative indices in Perl 6 is +different to that in Perl 5: + + # Perl 5... + ............_____________________________.................. + : | | | | | | : : + .....:.....|_____|_____|_____|_____|_____|.....:.....:..... + [0] [1] [2] [3] [4] [5] [6] [7] + [-7] [-6] [-5] [-4] [-3] [-2] [-1] + + + # Perl 6... + ............_____________________________.................. + : | | | | | | : : + .....:.....|_____|_____|_____|_____|_____|.....:.....:..... + [-2] [-1] [0] [1] [2] [3] [4] [5] [6] [7] + [*-7] [*-6] [*-5] [*-4] [*-3] [*-2] [*-1] [*+0] [*+1] [*+2] + +The Perl 6 semantics avoids indexing discontinuities (a source of subtle +runtime errors), and provides ordinal access in both directions at both +ends of the array. + +=head1 Mixing subscripts + +Occasionally it's convenient to be able to mix standard and user-defined +indices in a single look-up. + +Within a C<.[]> indexing operation you can use C<*{$idx}> to +convert a user-defined index C<$idx> to a standard index. That is: + + my @lengths{ Months } = (31,28,31,30,31,30,31,31,30,31,30,31); + + @lengths[ 2 .. *{Oct} ] # Same as: @lengths[ 2 .. 9 ] + +Similarly, within a C<.{}> indexing operation you can use C<*[$idx]> +to convert from standard indices to user-defined: + + @lengths{ *[2] .. Oct } # Same as: @lengths{ Jan .. Oct } + +In other words, when treated as an array within an indexing +operation, C<*> allows you to convert between standard and +user-defined indices, by acting like an array of the indices +of the indexed array. This is especially useful for mixing +standard and user-defined indices within multidimensional +array look-ups: + + # First three business hours of every day in December... + @calendar{Dec; *; *[0..2]} + + # Last three business hours of first three days in July... + @calendar[*{July}; 0..2; *-3..*-1] + +Extending this feature, you can use C<**> within an indexing operation +as if it were a multidimensional array of I<all> the indices of a fixed +number of dimensions of the indexed array: + + # Last three business hours of first three days in July... + @calendar{ July; **[0..2; *-3..*-1] } + + # Same... + @calendar[ **{July; 1..3}; *-3..*-1] =head1 PDL support An array C<@array> can be tied to a PDL at declaration time: my num @array[@@mytensorshape] is PDL; - my @array is PDL(:shape(^2;^2;^2;^2)) of int8; + my @array is PDL(:shape(2;2;2;2)) of int8; PDLs are allowed to assume a type of C<num> by default rather than the usual simple scalar. (And in general, the type info is merely @@ -404,7 +738,7 @@ is deliberately declared with a different dimensionality to provide a different "view" on the actual value: - my int @array[^2;^2] is Puddle .= new(:shape(^4) <== 0,1,2,3); + my int @array[2;2] is Puddle .= new(:shape(4) <== 0,1,2,3); Again, reconciling those ideas is up to the implementation, C<Puddle> in this case. The traits system is flexible enough to pass any @@ -434,6 +768,7 @@ @x[0;1;42] + =head1 The semicolon operator At the statement level, a semicolon terminates the current expression. @@ -450,7 +785,7 @@ all the dimensions; if you don't, the unspecified dimensions are "wildcarded". Supposing you have: - my num @nums[^3;^3;^3]; + my num @nums[3;3;3]; Then @@ -466,7 +801,7 @@ But you should maybe write the last form anyway just for good documentation, unless you don't actually know how many more dimensions -there are. For that case you may use C<**>: +there are. For that case use C<**>: @nums[0,1,2;**] @@ -524,7 +859,6 @@ 0 .. Inf :by(2) That's why we have C<..*> to mean C<..Inf>. - =head1 PDL signatures To rewrite a Perl 5 PDL definition like this: @@ -744,16 +1078,25 @@ =head1 Hashes -Everything we've said for arrays applies to hashes as well, except that -if you're going to limit the keys of one dimension of a hash, you have -to provide an explicit list of keys to that dimension of the shape, -or an equivalent range: +Like arrays, you can specify hashes with multiple dimensions and fixed +sets of keys: + + my num %hash{<a b c d e f>}; # Only valid keys are 'a'..'f' + my num %hash{'a'..'f'}; # Same thing + + my %rainfall{ Months; 1..31 } # Keys: Jan..Dec ; 1..31 - my num %hash{<a b c d e f>; Str}; - my num %hash{'a'..'f'; Str}; # same thing +Unlike arrays, you can also specify a hash dimension via a non- +enumerated type, which then allows all values of that type as keys in +that dimension: + + my num %hash{<a b c d e f>; Str}; # 2nd dimension key may be any string + my num %hash{'a'..'f'; Str}; # Same thing + + my %rainfall{ Months; Int }; # Keys: Jan..Dec ; any integer To declare a hash that can take any object as a key rather than -just a string, say something like: +just a string or integer, say something like: my %hash{Any}; my %hash{*}; @@ -762,7 +1105,7 @@ my %hash{**}; -As with arrays, you can limit the keys to objects of particular types: +You can limit the keys to objects of particular types: my Fight %hash{Dog; Cat where {!.scared}}; @@ -785,8 +1128,6 @@ In list context, it returns a lazy list fed by the iterator. It must be possible for a hash to be in more than one iterator at a time, as long as the iterator state is stored in a lazy list. -However, there is only one implicit iterator (the C<each> iterator) -that works in scalar context to return the next pair. [Or maybe not.] The downside to making a hash autosort via the iterator is that you'd have to store all the keys in sorted order, and resort it when the @@ -829,60 +1170,4 @@ This rule applies to C<Array>, C<Hash>, and C<Scalar> container objects. -=head1 Negative subscript dwimmery - -It has become the custom to use negative subscripts to indicate counting -from the end of an array. This is still supported, but only for unshaped -arrays: - - my @a1 = 1,2,3; - my @a2[*] = 1,2,3; - @a1[-1] # 3 - @a1[-0.5] # also 3 (uses floor semantics) - @a2[-1] # ERROR - @a2[-0.0001] # ERROR - @a2[0.0001] # 1 - -For shaped arrays you must explicitly refer to the current endpoint -using C<*>, the C<Whatever> object: - - @a2[*-1] # 3 - @a2[+*] = 1 # same as push(@a2, 1) - -When you use C<*> with C<+> and C<->, it creates a value of C<Whatever> -but C<Num>, which the array subscript interpreter will interpret as the -subscript one off the end of that dimension of the array. The lower -right corner of a two dimensional array is C<@array[*-1; *-1]>. - -This policy has the fortuitous outcome that arrays declared with negative -subscripts will never interpret negative subscripts as relative to the end: - - my @array[-5..5]; - @array[-1]; # always the 4th element, not the 11th. - @array[*-1]; # always the 11th element, not the 4th. - -Oddly, this gives us a canonical way to get the last element, but no -canonical way to get the first element, unless - - @array[*-*]; - -works... - -Alternately, C<*+0> is the first element, and the subscript dwims -from the front or back depending on the sign. That would be more -symmetrical, but makes the idea of C<*> in a subscript a little more -distant from the notion of "all the keys", which would be a loss, -and potentially makes C<+*> not mean the number of keys. - -Conjecture: we might provide a way to declare a modular subscript that -emulates the dwimmery, perhaps by using a subset type: - - subset Mod10 of Int where ^10; - my @array[Mod5]; - @array[42] = 1; # sets @array[2] - @array[582]; # returns 1 - -But perhaps C<Mod10> should work just like C<^10>, and the modular behavior -requires some extra syntax. - =for vim:set expandtab sw=4: