The text of this proposal is available at Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub (I'm not yet confident enough for the whole pull-request stuff ;-)
Depending on the time you read this, the Wiki-version might lack some typos and other errors...
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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub
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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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
}
}
...
}
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub
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>
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub extension: Limitations
It would nice to have where-clauses on the parameters to disallow certain values or value-combinations.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub 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.
<Compile time parameters · SwiftInofficialEvolution/Home Wiki · GitHub
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.