Proposal: Contiguous Variables (A.K.A. Fixed Sized Array Type)


(Justin Kolb) #1

To better support interfacing with lower level systems, like graphics
libraries for example, it would be helpful to support the concept of
contiguous variables. The most common use case for this would be to create
a Matrix struct that can be passed as data into something like Metal. This
can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
    var m00: Float
    var m01: Float
    var m10: Float
    var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
    var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
    var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of
elements and also for the data to be easily passed into graphics libraries
as their memory layout is somewhat predictable using sizeof, strideof, and
alignof. The downside is that you lose the ability to easily subscript or
iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile
time enforce a fixed number of elements and is not as easily passed into a
library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
    var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The
specific number of values must be a compile time constant. The only needed
functionality includes `count`, `subscript`, and iteration. To make things
easier to implement and to help avoid confusion and more complex
documentation, multiple dimensions are not allowed. To define multiple
dimensions you must provide your own ordering by wrapping this type in
another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
    var m: Float:2*2

    static let rows = 2
    static let columns = 2

    subscript(row: Int, column: Int) -> Float {
        return m[column * Matrix2x2.rows + row]
    }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the
rest of the data but is available and can also be used to do runtime bounds
checking.

struct Vector3 {
    var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed
sized arrays within them.


(Andrew Trick) #2

To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
    var m00: Float
    var m01: Float
    var m10: Float
    var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
    var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
    var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.

+1 for fixed size arrays, like option 3, but instead of using Array<T> shorthand, using an explicit FixedArray<T> type.

Andy

···

On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org> wrote:

Contiguous Variables:

struct Matrix2x2 {
    var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
    var m: Float:2*2

    static let rows = 2
    static let columns = 2

    subscript(row: Int, column: Int) -> Float {
        return m[column * Matrix2x2.rows + row]
    }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.

struct Vector3 {
    var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed sized arrays within them.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #3

Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

···

On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org> wrote:

To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
    var m00: Float
    var m01: Float
    var m10: Float
    var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
    var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
    var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
    var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
    var m: Float:2*2

    static let rows = 2
    static let columns = 2

    subscript(row: Int, column: Int) -> Float {
        return m[column * Matrix2x2.rows + row]
    }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.

struct Vector3 {
    var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed sized arrays within them.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Howard Lovatt) #4

You could make either option 1 or 2 conform to SequenceType and provide
them in the library for common types like small vectors and small matrices.
That would probably cover a lot of use cases.

In the longer term there is a thread on Swift evolution for computable
types which would address this issue.

···

On Thursday, 28 January 2016, Justin Kolb via swift-evolution < swift-evolution@swift.org> wrote:

To better support interfacing with lower level systems, like graphics
libraries for example, it would be helpful to support the concept of
contiguous variables. The most common use case for this would be to create
a Matrix struct that can be passed as data into something like Metal. This
can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
    var m00: Float
    var m01: Float
    var m10: Float
    var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
    var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
    var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of
elements and also for the data to be easily passed into graphics libraries
as their memory layout is somewhat predictable using sizeof, strideof, and
alignof. The downside is that you lose the ability to easily subscript or
iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile
time enforce a fixed number of elements and is not as easily passed into a
library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
    var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The
specific number of values must be a compile time constant. The only needed
functionality includes `count`, `subscript`, and iteration. To make things
easier to implement and to help avoid confusion and more complex
documentation, multiple dimensions are not allowed. To define multiple
dimensions you must provide your own ordering by wrapping this type in
another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
    var m: Float:2*2

    static let rows = 2
    static let columns = 2

    subscript(row: Int, column: Int) -> Float {
        return m[column * Matrix2x2.rows + row]
    }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the
rest of the data but is available and can also be used to do runtime bounds
checking.

struct Vector3 {
    var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed
sized arrays within them.

--
  -- Howard.


(Brad Hilton) #5

The Completing Generics Manifesto suggests a more universal solution with generic value parameters which is my vote:
Generic value parameters

Currently, Swift’s generic parameters are always types. One could imagine allowing generic parameters that are values, e.g.,

struct MultiArray<T, let Dimensions: Int> { // specify the number of dimensions to the array
  subscript (indices: Int...) -> T {
    get {
      require(indices.count == Dimensions)
      // ...
    }
}

A suitably general feature might allow us to express fixed-length array or vector types as a standard library component, and perhaps also allow one to implement a useful dimensional analysis library. Tackling this feature potentially means determining what it is for an expression to be a “constant expression” and diving into dependent-typing, hence the “maybe”.

···

To better support interfacing with lower level systems, like graphics
libraries for example, it would be helpful to support the concept of
contiguous variables. The most common use case for this would be to create
a Matrix struct that can be passed as data into something like Metal. This
can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
var m00: Float
var m01: Float
var m10: Float
var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
var m: [Float]
}

Options 1&2 allow for the compiler to enforce the fixed number of
elements and also for the data to be easily passed into graphics libraries
as their memory layout is somewhat predictable using sizeof, strideof, and
alignof. The downside is that you lose the ability to easily subscript or
iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile
time enforce a fixed number of elements and is not as easily passed into a
library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The
specific number of values must be a compile time constant. The only needed
functionality includes `count`, `subscript`, and iteration. To make things
easier to implement and to help avoid confusion and more complex
documentation, multiple dimensions are not allowed. To define multiple
dimensions you must provide your own ordering by wrapping this type in
another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
var m: Float:2*2

static let rows = 2
static let columns = 2

subscript(row: Int, column: Int) ->Float {
return m[column * Matrix2x2.rows + row]
}
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the
rest of the data but is available and can also be used to do runtime bounds
checking.

struct Vector3 {
var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed
sized arrays within them.


(Jacob Bandes-Storch) #6

I like this idea, but the syntax seems dangerously close to a call site for
"func *(lhs: Int, rhs: Any.Type)" (which is obviously ill-advised, but it
is allowed).

Maybe we could take advantage of something which would be very invalid
under the current grammar, namely (n T) rather than (n * T):

    let values: (4 Int) = (1, 2, 3, 4)

Jacob

···

On Thu, Jan 28, 2016 at 2:20 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

Rather than introduce a new kind of declaration, I think we could add a
few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n
elements, and
- We could allow tuples to be subscriptable, producing a value of the
common supertype of its elements.

That would make the experience of working with imported C types a lot
better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution < > swift-evolution@swift.org> wrote:
>
> To better support interfacing with lower level systems, like graphics
libraries for example, it would be helpful to support the concept of
contiguous variables. The most common use case for this would be to create
a Matrix struct that can be passed as data into something like Metal. This
can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of
elements and also for the data to be easily passed into graphics libraries
as their memory layout is somewhat predictable using sizeof, strideof, and
alignof. The downside is that you lose the ability to easily subscript or
iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile
time enforce a fixed number of elements and is not as easily passed into a
library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The
specific number of values must be a compile time constant. The only needed
functionality includes `count`, `subscript`, and iteration. To make things
easier to implement and to help avoid confusion and more complex
documentation, multiple dimensions are not allowed. To define multiple
dimensions you must provide your own ordering by wrapping this type in
another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with
the rest of the data but is available and can also be used to do runtime
bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed
sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #7

+1

-- E

···

On Jan 28, 2016, at 3:20 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org> wrote:

To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
   var m00: Float
   var m01: Float
   var m10: Float
   var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
   var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
   var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
   var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
   var m: Float:2*2

   static let rows = 2
   static let columns = 2

   subscript(row: Int, column: Int) -> Float {
       return m[column * Matrix2x2.rows + row]
   }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.

struct Vector3 {
   var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed sized arrays within them.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #8

Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

+1 to these features.

···

On Jan 28, 2016, at 4:20 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

That would make the experience of working with imported C types a lot better.

-Joe

On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org> wrote:

To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:

Current Option 1:

struct Matrix2x2 {
   var m00: Float
   var m01: Float
   var m10: Float
   var m11: Float
}

OR
Current Option 2:

struct Matrix2x2 {
   var m: (Float, Float, Float, Float)
}

OR
Current Option 3:

struct Matrix2x2 {
   var m: [Float]
}

Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.

Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.

Contiguous Variables:

struct Matrix2x2 {
   var m: Float:2*2
}

The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:

struct RowMajorMatrix2x2 {
   var m: Float:2*2

   static let rows = 2
   static let columns = 2

   subscript(row: Int, column: Int) -> Float {
       return m[column * Matrix2x2.rows + row]
   }
}

sizeof(Matrix2x2) is 16
strideof(Matrix2x2) is 16

m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.

struct Vector3 {
   var v: Float:3
}

sizeof(Vector3) is 12
strideof(Vector3) is 12

C code should also now be able to expose data types that contain fixed sized arrays within them.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #9

Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

I've thought about these two enhancements several times during the last few weeks, exactly as you describe them. +1.

···

--
Brent Royal-Gordon
Architechies


(Howard Lovatt) #10

+1

···

On 29 January 2016 at 09:20, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

Rather than introduce a new kind of declaration, I think we could add a
few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n
elements, and
- We could allow tuples to be subscriptable, producing a value of the
common supertype of its elements.

That would make the experience of working with imported C types a lot
better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution < > swift-evolution@swift.org> wrote:
>
> To better support interfacing with lower level systems, like graphics
libraries for example, it would be helpful to support the concept of
contiguous variables. The most common use case for this would be to create
a Matrix struct that can be passed as data into something like Metal. This
can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of
elements and also for the data to be easily passed into graphics libraries
as their memory layout is somewhat predictable using sizeof, strideof, and
alignof. The downside is that you lose the ability to easily subscript or
iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile
time enforce a fixed number of elements and is not as easily passed into a
library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The
specific number of values must be a compile time constant. The only needed
functionality includes `count`, `subscript`, and iteration. To make things
easier to implement and to help avoid confusion and more complex
documentation, multiple dimensions are not allowed. To define multiple
dimensions you must provide your own ordering by wrapping this type in
another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with
the rest of the data but is available and can also be used to do runtime
bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed
sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.


(Milos Rankovic) #11

This could potentially convert to Swift many library developers whose sanity depends on type guaranteed sequence sizes. I was just playing with Swift 2.2 along these lines, and got surprised how far I could get towards that ideal, see: http://codereview.stackexchange.com/q/124797/54297

For example, I got this to compile:

    let v = Vector<__<_4,_2>,Int>(0)
    v.elements.count // 42

    let w = Vector<_3,Int>(0)
    w.elements // [0, 0, 0]
    w[2].value = 5

    for x in w {
        x // 0, 0, 5
    }

Once created, `v` and `w` here can never change size, not even from within the implementation.

milos

···

On 4 Apr 2016, at 21:10, Brad Hilton via swift-evolution <swift-evolution@swift.org> wrote:

The Completing Generics Manifesto suggests a more universal solution with generic value parameters which is my vote:
Generic value parameters

Currently, Swift’s generic parameters are always types. One could imagine allowing generic parameters that are values, e.g.,

struct MultiArray<T, let Dimensions: Int> { // specify the number of dimensions to the array
  subscript (indices: Int...) -> T {
    get {
      require(indices.count == Dimensions)
      // ...
    }
}

A suitably general feature might allow us to express fixed-length array or vector types as a standard library component, and perhaps also allow one to implement a useful dimensional analysis library. Tackling this feature potentially means determining what it is for an expression to be a “constant expression” and diving into dependent-typing, hence the “maybe”.

> To better support interfacing with lower level systems, like graphics
> libraries for example, it would be helpful to support the concept of
> contiguous variables. The most common use case for this would be to create
> a Matrix struct that can be passed as data into something like Metal. This
> can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1&2 allow for the compiler to enforce the fixed number of
> elements and also for the data to be easily passed into graphics libraries
> as their memory layout is somewhat predictable using sizeof, strideof, and
> alignof. The downside is that you lose the ability to easily subscript or
> iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile
> time enforce a fixed number of elements and is not as easily passed into a
> library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The
> specific number of values must be a compile time constant. The only needed
> functionality includes `count`, `subscript`, and iteration. To make things
> easier to implement and to help avoid confusion and more complex
> documentation, multiple dimensions are not allowed. To define multiple
> dimensions you must provide your own ordering by wrapping this type in
> another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) ->Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with the
> rest of the data but is available and can also be used to do runtime bounds
> checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed
> sized arrays within them.
>
>
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #12

Sure, or we could lift (4 x Int) from LLVM IR's syntax.

-Joe

···

On Jan 28, 2016, at 2:36 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

I like this idea, but the syntax seems dangerously close to a call site for "func *(lhs: Int, rhs: Any.Type)" (which is obviously ill-advised, but it is allowed).

Maybe we could take advantage of something which would be very invalid under the current grammar, namely (n T) rather than (n * T):

    let values: (4 Int) = (1, 2, 3, 4)


(Joe Groff) #13

It dawns on me that the proposal will most likely need to introduce a syntax to initialize the tuple as well, since we can't just memset it.

Well, you can initialize it as (0,0,0,0,...), though that doesn't scale well. This seems like a separable issue, though. At least for C types, a zeroing initializer is provided by the Clang importer.

-Joe

···

On Jan 28, 2016, at 8:43 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Félix

Le 28 janv. 2016 à 23:33:59, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

+1

On 29 January 2016 at 09:20, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #14

It dawns on me that the proposal will most likely need to introduce a syntax to initialize the tuple as well, since we can't just memset it.

Félix

···

Le 28 janv. 2016 à 23:33:59, Howard Lovatt via swift-evolution <swift-evolution@swift.org> a écrit :

+1

On 29 January 2016 at 09:20, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #15

To me, it's closely related to the declaration issue. By introducing a (N x Type) syntax, we're solving the LHS problem but leaving the RHS problem intact. If we want to spin it off into a separate proposal, I think that the most logical split is to have a proposal for subscripts on uniform tuples, and a a proposal for the shorthand syntax and a way to initialize values of these types.

Félix

···

Le 29 janv. 2016 à 00:13:13, Joe Groff <jgroff@apple.com> a écrit :

On Jan 28, 2016, at 8:43 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

It dawns on me that the proposal will most likely need to introduce a syntax to initialize the tuple as well, since we can't just memset it.

Well, you can initialize it as (0,0,0,0,...), though that doesn't scale well. This seems like a separable issue, though. At least for C types, a zeroing initializer is provided by the Clang importer.

-Joe

Félix

Le 28 janv. 2016 à 23:33:59, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

+1

On 29 January 2016 at 09:20, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #16

Is this at all possible?

let values: (N x T) = V

where V.dynamicType is constrained to T and only one repeating value can be used with this shorthand? I'd imagine the single value would be 0 or maybe in rare circumstances 0xFF. My other common circumstance, passing a 4-tuple of floats, would be easy to write-up using the previous approach:

let values: (N x T) = (V0, ..., VN-1)

I imagine a single-value assignment would apply to the n-dimensional case as well:

let values: (M x (N x T)) = V

-- E

···

On Jan 28, 2016, at 10:13 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 28, 2016, at 8:43 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

It dawns on me that the proposal will most likely need to introduce a syntax to initialize the tuple as well, since we can't just memset it.

Well, you can initialize it as (0,0,0,0,...), though that doesn't scale well. This seems like a separable issue, though. At least for C types, a zeroing initializer is provided by the Clang importer.

-Joe

Félix

Le 28 janv. 2016 à 23:33:59, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

+1

On 29 January 2016 at 09:20, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Rather than introduce a new kind of declaration, I think we could add a few small features to tuples:

- We could say the type (n * T) is equivalent to a homogeneous tuple of n elements, and
- We could allow tuples to be subscriptable, producing a value of the common supertype of its elements.

That would make the experience of working with imported C types a lot better.

-Joe

> On Jan 27, 2016, at 7:50 PM, Justin Kolb via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> To better support interfacing with lower level systems, like graphics libraries for example, it would be helpful to support the concept of contiguous variables. The most common use case for this would be to create a Matrix struct that can be passed as data into something like Metal. This can be accomplished now, using something like the following:
>
> Current Option 1:
>
> struct Matrix2x2 {
> var m00: Float
> var m01: Float
> var m10: Float
> var m11: Float
> }
>
> OR
> Current Option 2:
>
> struct Matrix2x2 {
> var m: (Float, Float, Float, Float)
> }
>
> OR
> Current Option 3:
>
> struct Matrix2x2 {
> var m: [Float]
> }
>
> Options 1 & 2 allow for the compiler to enforce the fixed number of elements and also for the data to be easily passed into graphics libraries as their memory layout is somewhat predictable using sizeof, strideof, and alignof. The downside is that you lose the ability to easily subscript or iterate the elements.
>
> Option 3 does allow subscripting and iteration, but does not at compile time enforce a fixed number of elements and is not as easily passed into a library that expects to receive the raw data of the matrix.
>
>
> Contiguous Variables:
>
> struct Matrix2x2 {
> var m: Float:2*2
> }
>
> The variable `m` represents a series of 4 contiguous Float values. The specific number of values must be a compile time constant. The only needed functionality includes `count`, `subscript`, and iteration. To make things easier to implement and to help avoid confusion and more complex documentation, multiple dimensions are not allowed. To define multiple dimensions you must provide your own ordering by wrapping this type in another type and providing a custom subscript implementation. For example:
>
> struct RowMajorMatrix2x2 {
> var m: Float:2*2
>
> static let rows = 2
> static let columns = 2
>
> subscript(row: Int, column: Int) -> Float {
> return m[column * Matrix2x2.rows + row]
> }
> }
>
> sizeof(Matrix2x2) is 16
> strideof(Matrix2x2) is 16
>
> m.count is essentially a compile time constant and is not stored with the rest of the data but is available and can also be used to do runtime bounds checking.
>
> struct Vector3 {
> var v: Float:3
> }
>
> sizeof(Vector3) is 12
> strideof(Vector3) is 12
>
> C code should also now be able to expose data types that contain fixed sized arrays within them.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #17

Just spitballing…we could bring the N x <expr> syntax to expressions as well, so that (5 x 0) is (0,0,0,0,0), and maybe (1, 3 x 0, 2) is (1, 0, 0, 0, 2). With integer literals it's hard to tell which side is which, though...

-Joe

···

On Jan 28, 2016, at 9:24 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

To me, it's closely related to the declaration issue. By introducing a (N x Type) syntax, we're solving the LHS problem but leaving the RHS problem intact. If we want to spin it off into a separate proposal, I think that the most logical split is to have a proposal for subscripts on uniform tuples, and a a proposal for the shorthand syntax and a way to initialize values of these types.


(Joe Groff) #18

Yeah, and I could definitely see '[a, b, c, 253 x 0]' being useful to initialize dynamic arrays too.

-Joe

···

On Jan 29, 2016, at 11:11 AM, Trent Nadeau <tanadeau@gmail.com> wrote:

The fact that this could be used in expressions is making the N x T syntax grow on me.

(N x T) := (T, T, ...)
(N x <expr>) := (<expr>, <expr>, ...)


(David Sweeris) #19

Speaking of spitballing, these can be nested/composed, right?
let x = (3 x (0, 2 x “”), 2 x NSFileManager())

So, I think that this would be correct:
let x0 = x.0 // is ((Int, (String, String)), (Int, (String, String)), (Int, (String, String)))
let x1 = x.1 // is (NSFileManager, NSFileManager)
let xIdx0 = x.0[0] // is (Int, (String, String))

Can we implicitly “unzip” the implicit "arrays”, as well?
let x00 = x.0.0 // is (Int, Int, Int)… equivalent to let x00 = x.0.map { $0.0 }
let x01 = x.0.1 // is ((String, String), (String, String), (String, String))… equivalent to let x01 = x.0.map { $0.1 }

- Dave Sweeris

···

On Jan 29, 2016, at 10:43, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 28, 2016, at 9:24 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

To me, it's closely related to the declaration issue. By introducing a (N x Type) syntax, we're solving the LHS problem but leaving the RHS problem intact. If we want to spin it off into a separate proposal, I think that the most logical split is to have a proposal for subscripts on uniform tuples, and a a proposal for the shorthand syntax and a way to initialize values of these types.

Just spitballing…we could bring the N x <expr> syntax to expressions as well, so that (5 x 0) is (0,0,0,0,0), and maybe (1, 3 x 0, 2) is (1, 0, 0, 0, 2). With integer literals it's hard to tell which side is which, though...

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #20

I'm not quite happy with this. Consider this case:

// In some library...
typealias FixedSizeBuffer = (256 x CChar)

// In my code...
var buffer: FixedSizeBuffer = /*???*/
generateCryptographicNoise(&buffer)

I shouldn't really have to know that 'buffer' has 256 elements.

Throwing out some possibilities:

var buffer: FixedSizeBuffer = (0...)
var buffer: FixedSizeBuffer = (* x 0)
var buffer: FixedSizeBuffer = (repeat 0)

…none of which I'm particularly happy with. (The first one is inconsistent with the not-actually-proposed syntax for forwarding variadics.)

Jordan

···

On Jan 29, 2016, at 10:43, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 28, 2016, at 9:24 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

To me, it's closely related to the declaration issue. By introducing a (N x Type) syntax, we're solving the LHS problem but leaving the RHS problem intact. If we want to spin it off into a separate proposal, I think that the most logical split is to have a proposal for subscripts on uniform tuples, and a a proposal for the shorthand syntax and a way to initialize values of these types.

Just spitballing…we could bring the N x <expr> syntax to expressions as well, so that (5 x 0) is (0,0,0,0,0), and maybe (1, 3 x 0, 2) is (1, 0, 0, 0, 2). With integer literals it's hard to tell which side is which, though...