Limitations of Generic Types
Generic types introduce a namespace that is underutilized. All members of the type are bound to the generic context even if they make no use of the generic arguments. This limitation is unfortunate in a number of practical scenarios.
Stored properties
struct Foo<T> {
// error: Static stored properties not supported in generic types
static let message = "hello"
}
The intent here is to create a constant within the namespace of Foo
. The error can be worked around by using a computed property instead:
struct Foo<T> {
static var message: String { return "hello" }
}
Static member access
Unfortunately message
still cannot be used as desired:
// error: Generic parameter 'T' could not be inferred
let x = Foo.message
An arbitrary generic argument must be specified in order to use the constant. The requirement to specify a generic argument is also unfortunate in the case of factory methods:
extension Foo {
static func makeFooOfInt() -> Foo<Int> { return Foo<Int>() }
}
// elsewhere:
// error: Generic parameter 'T' could not be inferred
let fooOfInt = Foo.makeFooOfInt()
Strange factories
The signature of the factory method does not depend on T
. Requiring the generic argument to be specified can lead to strange and misleading (but valid) code such as:
extension Foo {
static func makeDefault() -> Foo<Int>
}
// elsewhere:
let foo = Foo<String>.makeDefault()
What type does foo
have here? Many programmers would read this and assume the type is Foo<String>
but it is actually Foo<Int>
.
Nested types
In some cases it would also be nice to use the "namespace" of a generic type to declare nested types or type aliases that do not depend on the generic arguments of the generic type.
Introducing shared
members
This pitch introduces shared
members of generic types.
struct Foo<T> {
shared let message = "hello"
shared func makeDefault() -> Foo<Int> { ... }
shared struct Bar {}
}
let message = Foo.message
let foo = Foo.makeDefault()
let bar = Foo.Bar()
Metatypes
Generic types would have a shared metatype Foo.self: Foo.Type
. This shared metatype would provide storage for properties. In order to make the shared metatype available in a generic context (even when it is not known whether T
is generic or not), all types would have a Shared
associated type: Foo.Type == Foo.Shared == Foo<Int>.Shared == Foo<String>.Shared
. The instance of the shared metatype would be available via the shared
property of metatypes: Foo.self == Foo.self.shared == Foo<Int>.self.shared == Foo<String>.self.shared
.
Note: for metatypes of non-generic types: T.Type == T.Shared
and T.self == T.self.shared
.
Protocol conformance
It would also be possible to conform the shared type to protocols using a shared extension:
protocol P {
static func doSomething()
}
shared extension Foo: MyProtocol {
func doSomething()
}
Conformance would only be possible for protocols that do not have instance or initializer requirements because it would not be possible to fulfill all requirements of such a protocol. One edge case does exist here however: protocols which supply default implementations of all instance requirements.
The above example also demonstrates that shared
is implicit for all members of a shared
extension. This syntactic sugar could be mirrored with a similarly behaving static extension
where all members are implicitly static
.
Higher-Kinded Types
There is one significant additional use case. A shared, guaranteed-to-be-unique type is exactly what is necessary in order to produce a (nearly) bulletproof and convenient-as-possible encoding of higher-kinded types in Swift.
Note: the features described here should not be considered or accepted with motivation focused on a workaround for higher-kinded types. They should be added directly to the core language someday. This section exists primarily to point out an interesting application and not to motivate the features described here directly.
Bikeshedding
shared
was name I liked best as a working name after giving it a few minutes of thought. Other possibilities are common
and unbound
. I'm sure members of the community would love to come up with a long list of possibilities and help choose the best one.