Reputation: 9039
XCode 6: Beta 5:
Goal:
I am trying to write generic code for types that are semantically compatible but do not share (or appear to share) sufficient protocols to base my generics on a subset of shared protocols. So far, I have not been able to find a solution, and am wondering I am missing something or if it is a limitation of the language - any insight is appreciated.
Problem:
I have some functions that differ only by type and not by semantics and seem like a natural fit for generics. The problem that I am having, is that from what I can tell, Swift does what seems like parse-time binding of generics, failing if there could conceivably be a problem, and not when there actually is one.
Example:
Consider the following generic functions in a contrived example:
func defVal<T where T:FloatingPointType, T:FloatLiteralConvertible>(T.Type) -> T {
return 0.0
}
func defVal<T:IntegerLiteralConvertible>(T.Type) -> T {
return 0
}
Note that I have provided functions that should span the cases of integers and floats, and intentionally did not want to provide an exhaustive list of all possible variations that are of no relevance to me.
I then want to define generic code that spans types - in this example, int and float types. Note that this code fails to compile even in the absence of any code that calls it:
func doSomethingGeneric<T>(t:T) -> [T]
{
let a = defVal(T) // Type 'T' does not conform to protocol FloatLiteralConvertible
let b = a * a // works
return [t]
}
In my recollection, this would compile in C++ until you called it with an incompatible type, at which point the compiler would catch it.
I also tried other variants of diminished utility:
func doSomethingWithFloats<T
where T:FloatingPointType, T:FloatLiteralConvertible>(t:T) -> [T]
{
let a = defVal(T) // works
let b = a * a // T is not convertible to UInt8
// - need a floating point arithmetic type?
let c = -a // T is not convertible to Float
let f:Float = -a // T is not convertible to Float
return [t]
}
Given the sense that Swift provides protocols as a way of grouping concrete instances (specialized, not generic), I concocted a protocludge:
protocol Initializable {}
extension Float : Initializable {}
extension Double : Initializable {}
extension CGFloat : Initializable {}
func doSomethingWithInitializable<T:Initializable>(t:T) -> [T]
{
let a = defVal(T) // Type 'T' does not conform to protocol FloatLiteralConvertible
let b = a * a // works
return [t]
}
Note that this fails even though FloatLiteralConvertible is implemented across the set of all Initializable types. Put another way, Swift seems to be binding the generic types too early, and treating generic types as if they were specialized concrete instances instead of a greater pattern that would compile out further down the chain. Furthermore, note that while I could derive from FloatLiteralConvertible, this would preclude me from supporting int types etc. If there was a common ArithmeticType protocol, that could conceivably work, but I do not see anything of the sort. And this is the crux of the problem - there is no common protocol that works for both, even though both ints and floating types are semantically compatible (have the same rules of math).
So in summary, how do you write generic functions for which the types are semantically compatible, but for which there are not enough spanning protocols to filter by protocol (in this case - FloatingPointType does not implement IntegerArithmeticType, but is semantically capable of arithmetic).
Thanks in advance.
Upvotes: 2
Views: 1799
Reputation: 3329
Unlike C++, Swift does not deal with generics by substituting the concrete types at the call site and making a non-generic copy (at least in general it doesn't - as an optimization that's allowed, but I digress)
Swift deals with genericity by passing in metadata information describing the actual types at each invocation into one master function, which then uses metadata-provided entry points to manipulate your objects
In your example, Initializable does not provide any operations, so when the compiler tries to execute defVal(T) it has no clue what to do (how can it ensure that there is an overload of defVal for your type?)
What you want to do is actually define defVal as a static function on the Initializable protocol, and then implement it in the extensions, then Swift will know that T.defVal() means something akin to
metadata[Initializable]->defVal(metadata)
Oh, since you're trying to execute a *(T,T), you might also want to make a Multipliable protocol and then your T will be typed as
<T: protocol<Initializable,Multipliable>>
Upvotes: 6