I normally handle variation this way, and just deal with the lack of the selectable global function with some kind of “setup” at startup. However, even if this problem were solved (and note that this would be solved by Module Interfaces, you could define a Module Interface that only declares the protocol and a global function to return an instance), it has some problems.
First is performance. Probably not that important for most of us, but the game engine authors will find the cost of dynamic dispatch there potentially deal-breaking.
Second is semantics. Let’s say the Math protocol doesn’t work with primitives, but instead its own corresponding type that represents, say, real numbers:
protocol Real : ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
init?(_ stringValue: String)
var doubleValue: Double { get }
}
protocol Math {
func squareRoot(_ value: Real) -> Real
}
protocol func math() -> Math
struct Real1: Real {
init?(_ stringValue: String) {
…
}
init(integerLiteral int: Int) {
…
}
init(floatLiteral float: Double) {
…
}
var doubleValue: Double {
…
}
}
struct Math1: Math {
func squareRoot(_ value: Real) -> Real {
let value = value as! Real1 // Gross!
…
}
}
adopt protocol func math() -> Math {
Math1()
}
struct Real2: Real {
init?(_ stringValue: String) {
…
}
init(integerLiteral int: Int) {
…
}
init(floatLiteral float: Double) {
…
}
var doubleValue: Double {
…
}
}
struct Math2: Math {
func squareRoot(_ value: Real) -> Real {
let value = value as! Real2 // Gross!
…
}
}
adopt protocol func math() -> Math {
Math2()
}
(We'd also need a factory for Real)
Notice how you have to force downcast the related type to the “matching” concrete implementation. This design does not express that if Math1 exists in the program, the only Real that can exist is Real1. We’re really incorrectly expressing that any Math can work with any Real, but that’s not true.
“Make Real an associatedtype of Math!”
If we did that, we correctly express what each Math can work with, but we lose the ability to work with Math abstractly. The client has to know the concrete Math it’s working with, but we’re trying to write libraries that can work with any Math library.
With module interfaces it would look like this. First the module interface:
struct Real : ExpressibleByIntLiteral, ExpressibleByFloatLiteral {
init?(_ stringValue: String)
var doubleValue: Double { get }
}
struct Math {
func squareRoot(_ value: Real) -> Real
}
func math() -> Math
Then an implementing module:
public struct Real: ExpressibleByIntLiteral, ExpressibleByFloatLiteral {
public init?(_ stringValue: String) {
…
}
public init(integerLiteral int: Int) {
…
}
public init(floatLiteral float: Double) {
…
}
public var doubleValue: Double {
…
}
}
public struct Math {
public func squareRoot(_ value: Real) -> Real {
…
}
}
public func math() -> Math {
.init()
}
And another implementing module:
public struct Real: ExpressibleByIntLiteral, ExpressibleByFloatLiteral {
public init?(_ stringValue: String) {
…
}
public init(integerLiteral int: Int) {
…
}
public init(floatLiteral float: Double) {
…
}
public var doubleValue: Double {
…
}
}
public struct Math {
public func squareRoot(_ value: Real) -> Real {
…
}
}
public func math() -> Math {
.init()
}
Downcasting is gone. By declaring a particular concrete Real and Math together in a module, we’re expressing that they must come together. There also should be no runtime cost. The calls to Math.squareRoot should be statically wired to the particular Math linked in at link time.
We also could just expose a default init in Math instead of the global factory, or move the factory to be a static func on Math.