14

I recently tried my Array @a = 'a'..'z'; and my Array @a = @('a'..'z');.

Both will produce the following error:

Type check failed in assignment to @a; expected Array but got Str ("a")
in block <unit> at <unknown file> line 1

However initializing without the type works and seems to ultimately produce an Array:

> my @a = 'a'..'z';
> @a.^name
Array

Why is this the case?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Jessica Nowak
  • 849
  • 6
  • 13
  • 2
    I guess when you are typing an array, the type applies to each element in the array. So in your case the type of each element is not `Array` but `Str`. That's why you get the error `expected Array but got Str` when you type `@a` as `my Array @a`. – Håkon Hægland May 16 '19 at 05:40

4 Answers4

12

TL;DR I provide a relatively simple answer in Why is this the case? However, that explanation may be inadequate1 so I review some alternatives in Declare and initialize a typed array from a range.

Why is this the case?

  • my @a; declares a new Array (initialized to be empty) and "binds" it to the symbol @a. Thus my @a; say @a.^name returns Array. There is no need to use the word Array in a declaration or initialization of an array -- the @ is enough.2

  • my @a = 'a'..'z' attempts to copy each value in the range 'a' thru 'z', one at a time, into @a[0], @a[1], etc. The new array bound to @a has a type constraint for each of its elements (explained in the next section); it will be checked for each value (and will succeed).

  • my Array @a declares an Array with an Array type constraint on its elements (so it's an array of arrays). my Array @a; say @a.^name returns Array[Array] to indicate this. my Array @a = 'a'..'z'; fails when copying the first value ("a") because it is a Str value not an Array.

Declare and initialize a typed array from a range

my @a = 'a'..'z';

The my @a part of this statement declares a variable that's bound to (refers to) a new array of type Array. Because no element type constraint was specified, the new array's elements are constrained to be consistent with Mu, the Most unassuming type in P6. In other words it's an empty array ready to contain whatever values you want to put in it. (One could say that say @a.^name displays Array rather than Array[Mu] because the [Mu] is considered Most uninteresting.)

... = 'a'..'z' initializes the new array. The initialization has no impact on the array's already established type constraints. It just delivers copies of the strings 'a', 'b' etc. into the array (which auto-expands to receive them into @a[0], @a[1] etc.).

I recommend devs avoid adding explicit type constraints on variables and explicit coercions of values unless they're confident they're desirable. (cf my parenthetical remarks at the end of an earlier SO answer.) That said, you can choose to do so:

my Str @a = 'a'..'z';      # `Array` elements constrained to `Str`
my Str @a = (0..99)>>.Str; # Coerce value to match constraint

Alternatively, P6 supports explicit binding, rather than assignment, of a value or list of values. The most common way to do this is to use := instead of =:

my @a := 'a'..'z'; say @a.WHAT; say @a[25]; # (Range)␤z

Note how the explicit binding of @a means @a has not been bound to a new Array but instead to the Range value. And because a Range can behave as a Positional, positional subscripting still works.

The following statements would imo be grossly redundant explicit typing but would both work and yield exactly the same outcome as each other, though the first one would be faster:

my Str @a := Array[Str].new('a'..'z'); 
my Str @a  = Array[Str].new('a'..'z'); 

There's more to discuss about this topic but perhaps the foregoing provides enough for this question/answer. If not, please ask further questions in comments under your original question and/or below.

Footnotes

1 An earlier version of this answer began with:

my Array @a ...
# My array of thoughts raised by this declaration
# and your questing "why?" in this SO question
# began with wry thoughts about complicated answers
# about reasons your array is awry and pedances

(I made up the word "pedances" to mean something that appears to be pedantic but flows nicely when used correctly -- which will happen naturally once you've become familiar with its apparently idiosyncratic but actually helpful nature. More importantly, I needed something that rhymes with "answers".)

2 Here are a couple of mnemonics for the meaning of @ in P6:

  • It looks like a zero digit (0) with an (Mathematical Italic Small A) inside it -- and a @foo variable is by default a 0 indexed (or @).

  • It sounds like the word "at". An array has elements at indices.

raiph
  • 31,607
  • 3
  • 62
  • 111
7

Set the type of the element in the array:

my Str @a = 'a'..'z'; 
say @a; #[a b c d e f g

To see, what type it is, you can use .WHAT

my Str @a = 'a'..'z'; 
say @a.WHAT #(Array[Str])

To test if it is an array, you can smartmatch

my Str @a = 'a'..'z'; 
say 'is array' if @a ~~ Array; #is array

say 'is str array' if @a ~~ Array[Str]; #is str array

say 'is str array' if @a ~~ Array[Int]; #
LuVa
  • 2,288
  • 2
  • 10
  • 20
7

The @('a'..'z') will make a List not an Array. Changing it to ['a'..'z'] will give an Array.

But that will still give you a type error when assigning it to my Array @a as you have done.

If you want to assign an entire Array to an element of another array, you have to itemise it somehow eg:

$[1,2,3]; #Still an array but treated as single item
[1,2,3], ; #Note the list operator (comma), this give you a list of lists

So in your case:

'a'..'z'; is a range, needs to be converted to an array, so

['a'..'z']; range is now evaluated into an array

$['a'..'z']; range is now evaluated into an array and taken as a single item

my Array @a=$['a'..'z'];

say @a;

#OUTPUT:
#[[a b c d e f g h i j k l m n o p q r s t u v w x y z]]
# Which is an array of arrays;

Not sure if it is what you are after but it removes the type error.

drclaw
  • 2,463
  • 9
  • 23
1

When you declare an array, you can easily specify what type the elements of the array are.

my Str @a = 'a'..'z';

You can specify the exact class that is used for the storage as well.
The following line is exactly the same as the above line; since Array is the default storage class.

my @a is Array[Str] = 'a'..'z';

You can even combine the two.
This following is also exactly the same.

my Str @a is Array = 'a'..'z';

When you wrote the following line.

my Array @a = 'a'..'z';

What you were actually saying was:

my @a is Array[Array] = 'a'..'z';

I assume you thought you were writing this.

my @a is Array = 'a'..'z';

This can be useful if you don't want the elements to change.

my @a is List = 'a'..'z';

Or if you have specific needs that aren't satisfied by the default Array class.

use Array::Unique; # doesn't really exist yet (afaik)

my @a is Array::Unique[Str] = 'a'..'z';

my Str @b is Array::Unique  = 'a'..'z';
Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129