[swift-evolution] [Completing Generics] Generic value parameters

Tino Heth 2th at gmx.de
Wed Mar 9 05:16:12 CST 2016


I proposed adding generic value parameters under the label "compile time parameters", and stopped working on it because it is out of scope for Swift 3 — yet I'm still very eager to see this added in the future.
My draft most likely had no impact on "Completing Generics", but as I favored the same syntax, I take this match as an indication that the choice is somewhat natural.
Introduction

Generics are often seen as "simplified templates": They are not as powerful, but have the benefit of being less complicated and dangerous. The feature illustrated here should not only make Swift more powerful, but also safer by adding basic datatypes like fixed-size arrays.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#motivation>Motivation

Right now, we can have something like let m = Matrix(rows: 3, columns: 4) easily. There's just the problem that the compiler cannot deduce which matrices are "compatible", as there is only one Matrix-type. If we had a way to tell the compiler that "rows" and "columns" have an effect on the type, errors due to dimension mismatches could be eliminated (vector math, but also functions like zip would benefit from this).

Additionally, the proposal would make it possible to create unit systems, so that calculations are checked for matching quantities (so you cannot add a force to a velocity without causing an error).

Currently, there is no elegant way to declare a C-type array of a fixed size; this is a fundamental problem that could be solved with the syntax presented here.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#proposed-solution>Proposed solution

The basic idea is quite simple: We just need a way to tell the compile that there are some parameters that have impact on their type. On possible syntax would be

struct Matrix<T: ScalarType, let rows: UInt, let columns: UInt> {
...
subscript(row: UInt, column: UInt) -> T {
    set(value) {
        if row < rows && column < columns {
            // set the entry
        } else {
            // out of bounds - that is not allowed
        }
    }
...
}
 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#detailed-design>Detailed design

I think its easy to grasp with the example: In addition to type parameters, we introduce constants that are defined in a similar way:

let [identifier]: [type]

e.g.

struct FloatVector<let dimensions: UInt>...

Unlike templates, compile-time parameters (as the name already suggests) could live inside libs, without disclosing details about their implementation.

From the inside of the parametrized object, compile-time parameters would be used like normal let-parameters/members.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#instantiation>Instantiation

The behavior should be similar to generics, so the position of each parameter in the list is fixed. To make things clearer, I suggest to accept (optional) labels, so that the two following statements are valid:

let force: FloatVector<dimensions: 3>

let impulse: FloatVector<3>

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#limitations-for-parameter-values>Limitations for parameter values

Although integer-type parameters are most likely the only ones with a broad use case, any type implementing one of the ...LiteralCovertible protocols could be used. Enums and other entities could make sense as well, but this is beyond the scope of this proposal.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#possible-extension-limitations>Possible extension: Limitations

It would nice to have where-clauses on the parameters to disallow certain values or value-combinations.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#impact-on-existing-code>Impact on existing code

None, it's a new feature that does not affect existing code - but it might be a good opportunity to improve the generics syntax as well: There have also been wishes for labeled type parameters, and as far as I can see, those should be allowed, too.

To further increase consistency, the generics-syntax could be changed (struct Array<type T>... or struct Array<T: type where ...>), but that is not coupled with this proposal.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#alternatives-considered>Alternatives considered

Actually, the let-syntax is not my preferred choice - but there is a collision with generics, and relying on capitalization to distinguish MyClass<size: Int> and MyClass<V: UIView> seems strange. It could still be possible to drop let without confusing the compiler, but I'm concerned about confusing the user:

It makes no sense to declare a generic parameter that is restricted to a subtype of something that is final, but that isn't obvious to a human reader.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#marked-parameters>Marked parameters

It would be possible to keep the parameters in the initializer (or function) and mark them with a keyword: init(static dimensions: Int) {...

Especially for types, this would be cumbersome, as you can have many initializers.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#different-kind-of-braces>Different kind of braces

Instead of grouping value parameters with type parameters, they could be separated:

class Test[Size: Int]<T> or class Test(size: Int)<T>

The benefit would be that let isn't needed anymore, but as compile-time parameters are very similar to generics, it would be nice to resemble that in the syntax. Especially the square-braces have a very different, established meaning (array subscript), so I'd strongly advice not to consider them.

Normal parenthesis don't have that problem at the declaration site, but lack an obvious instantiation syntax that doesn't collide with init parameters.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#separate-types>Separate types

The current "solution" is declaring a type for every case you want to cover.

This approach works good enough in simple situations (e.g. Vector4), but it scales badly:

It is tedious to declare new variants, and the only way to express their tight resemblance is a coherent naming scheme that isn't enforced.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#macros-and-preprocessors>Macros and preprocessors

It is possible to generate distinct types using macros - but as there is no build-in support for those, this is quite cumbersome.

 <https://github.com/SwiftTypesafeCalculations/Home/wiki/Compile-time-parameters#tuples>Tuples

There has been a discussion about a shorthand to declare tuples with a fixed number of elements.

This solution would have the benefit of automatic compatibility with C-structs - but the downside of not having the power of Swift structs (and classes): Tuples can't have methods, and this is a major drawback.

So, the tuple-extension is no real alternative, but both ideas would fit together without redundancy.


In general, I think a Wiki-Page would be a nice helper to discuss comprehensive topics like generics (things like concurrency and compile-time evaluation imho are good candidates for a Wiki as well).


- Tino
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160309/64091b77/attachment.html>


More information about the swift-evolution mailing list