Swift's dot shorthand for static factories is wonderful! Unfortunately, it currently requires a concrete type context in order to work. A while ago I pitched an idea for lifting this limitation. It turned out that the original idea in that pitch isn't viable, but the thread did lead to some interesting discussion that pointed in a potentially more viable direction.
SwiftUI
SwiftUI is using the workaround I described in the form of StaticMember
. Desipite having developed the workaround and used it in a few projects, it took some head scratching before I recognized the role StaticMember
was playing in SwiftUI. Quoting the documentation:
Use StaticMember to support implicit member expressions, sometimes referred to as leading dot syntax, where they would normally not be supported, like static properties representing concrete values provided to a protocol-constrained generic function. For example:
protocol ColorStyle {
typealias Member = StaticMember<Self>
}
extension StaticMember where Base : ColorStyle {
static var red: RedStyle.Member { return .init(.init()) }
static var blue: BlueStyle.Member { return .init(.init()) }
}
extension View {
func colorStyle<S : ColorStyle>(_ style: S.Member) -> some View {
...
}
}
MyView().colorStyle(.red)
MyView().colorStyle(.blue)
As an Apple framework that will live a very long life, I don't think SwiftUI should be introducing a workaround like this. I think we should revisit this topic and introduce proper langauge support if at all possible. If one isn't possible we should consider moving StaticMember
into the standard library as a standard workaround everyone in the community can become familiar with and share.
Previous discussions and big picture
As I mentioned, the earlier thread on this topic pointed in some interesting directions and I continued to think about it. This thinking lead to a broad sketch for a direction in which we are able to extend metatypes and existentials in additiona to concrete types. Discussion of that sketch led to a design that uses the meta
keyword for extending metatypes. (It also builds on the any
and some
keywords that are a part of Improving the UI of generics)
At a high level, this design sketched at the above links enables the following forms of extensions (among others):
extension P
contionues to extend both conforming types and the existential as it does todayextension any P
extends a protocol existential only (not conforming types or the metatype)extension some P
extends conforming types onyl (not the existential or the metatype)extension meta P
extends the protocol metatype only (not the existentia or conforming types)
Proposed solution
I propose that we shoud introduce protocol metatype extensions and use those to drive dot shorthand in generic contexts. The above SwiftUI code would be streamlined as follows:
protocol ColorStyle {}
extension meta ColorStyle {
static var red: RedStyle { return .init() }
static var blue: BlueStyle { return .init() }
}
extension View {
func colorStyle<S : ColorStyle>(_ style: S) -> some View {
...
}
}
MyView().colorStyle(.red)
MyView().colorStyle(.blue)
// Static members of the protoco metatype may be accessed explicitly as well
let myStyle = ColorStyle.red
// Conforming values stored in variables are directly compatible with the generic API
// which is not the case with APIs based on a `StaticMember` wrapper type
MyView().colorStyle(myStyle)
In this design, dot shorthand will consider all static members of the protocol metatype which return a value of a conforming type. This removes the need for a wrapper type and streamlines the API of the library. It also improves support for passing variables of conforming types directly to an API.
Without direct language support for this shorthand a library author is left with two bad choices if it wishes to support both dot shorthand and direct values. One choice is to require users to wrap values of conforming types. The other is to provide two overloads, one taking a conforming value and the other using a wrapper to drive the use of dot shorthand. The first option forces additional syntax on users of the library. The latter introduces a lot of boilerplate into the library and creates potential for ambiguity. Both options may cause confusion for users of the library who may not understand the design.
In summary, let's bridge the gap between dot shorthand and generics so these language features are more directly compatibe with each other. (And let's do it before SwiftUI ships with a workaround)