Search code examples
arrayschapel

How to convert all array elements to a specified type


I think I can convert a variable foo to type T as foo:T, for example 1.0: real(32) to obtain a 32-bit real value from a 64-bit one. And I am wondering how I can make a similar conversion for all elements of an array simultaneously, e.g., to make an array of real(32) from an array of real(64). I have tried the following patterns, but only the pattern 2 seems to work for me, which is a bit tedious to write. Possibly, are there different ways for such a conversion, or am I making some mistakes in the other patterns below?

(Also, I am wondering if the word "convert" is appropriate for foo:T, or should I use the word "cast" for this kind of usage...?)

proc test(arr: [] real(32))
{
  writeln("arr = ", arr);
  writeln("type of arr = ", arr.type:string);
  writeln("type of arr[0] = ", arr[0].type:string);
}

// test( [1.0, 2.0] );  // Pattern 1: Error
//
//     error: unresolved call 'test([domain(1,int(64),one)] real(64))'
//            this candidate did not match: test(arr: [] real(32))
//            because an argument was incompatible

// test( [1.0:real(32), 2.0:real(32)] );  // Pattern 2: OK
//
//     output: arr = 1.0 2.0
//             type of arr = [domain(1,int(64),one)] real(32)
//             type of arr[0] = real(32)

// test( [1.0, 2.0]: [1..2] real(32) );  // Pattern 3: Error
//
//     error: illegal cast from [domain(1,int(64),one)] real(64)
//            to [domain(1,int(64),one)] real(32)

// test( [1.0, 2.0]: real(32) );  // Pattern 4: Error
//
//     error: unresolved call 'test(promoted expression)'
//            this candidate did not match: test(arr: [] real(32))
//            because actual argument #1 with type 'iterator'
//            is passed to formal 'arr: []'

// var arr_tmp : [1..2] real(32) = [1.0, 2.0];  // Pattern 5: Error
// test( arr_tmp );
//     error: cannot initialize a value of type 'real(32)' from a 'real(64)'

Solution

  • The patterns you're writing are very close to my preferred way(s) of writing this, where pattern 4 is the closest: test( [1.0, 2.0]: real(32) );. There are two main components to why this doesn't work:

    • The first is that array arguments in Chapel are passed by [const] ref by default, so the test() procedure wants an actual array, stored in memory, that it can refer to.
    • The second is that Chapel avoids manifesting arrays in memory unless the user explicitly requests it, to avoid the kinds of memory budgeting surprises that can pop up in HPC-scale codes when working with massive arrays. For that reason, when Chapel sees a cast like myArray: type, rather than manifesting a new array in memory for it, it will create an anonymous parallel iterator expression that implements the cast, equivalent to [elem in myArray] (elem: type) (where those square brackets can be read as forall elem in myArray do (elem: type) for an example like this).

    The combination of these things is where the problem comes in—the routine wants to reference an array, but the actual argument isn't actually an array and the language is unwilling to silently make it into one without user involvement.

    For such a routine, the fix is to explicitly manifest the array in memory, where two ways to do that would be:

    1. Declare an explicit array temporary as you did in Pattern 5. The problem there was that Chapel doesn't permit assignments from arrays-of-real64 to arrays-of-real32 without a cast. So if you were to write that declaration as:

      // Pattern 6: Promote cast across array elements, capture in inferred- type array
      var arr_tmp = [1.0, 2.0]: real(32); 
      test( arr_tmp );
      

      or:

      // Pattern 7: Promote cast across array elements, capture in explicitly-typed array
      var arr_tmp : [1..2] real(32) = [1.0, 2.0]: real(32);
      test( arr_tmp );
      

    and either of those should make this approach work.

    1. A more concise approach would be to add an explicit in intent to the formal argument to indicate that you want the routine to initialize the formal argument with the actual argument, effectively giving you the same thing as the above with less typing:

      // Pattern 7: Copy the argument into a new formal to realize the array
      proc test2(in arr: [] real(32))
      {
        writeln("arr = ", arr);
        writeln("type of arr = ", arr.type:string);
        writeln("type of arr[0] = ", arr[0].type:string);
      }
      
      test2( [1.0, 2.0]: real(32) );
      

    Here's an ATO session that captures both of these approaches: ATO

    With respect to this question:

    Also, I am wondering if the word "convert" is appropriate for foo:T, or should I use the word "cast" for this kind of usage...?

    I think of "cast" as being synonymous with "explicit conversion" and for "conversions" to also include "implicit conversions" (also sometimes referred to as "coercions"). An approach that uses : in a value expression, like pattern 6, is relying on a cast or explicit conversion. Meanwhile, pattern 7 is relying on an implicit conversion to convert the parallel iterator expression of the actual argument into the formal argument's array type.