init<Subject>(describing instance: Subject) where Subject : TextOutputStreamable
init<Subject>(describing instance: Subject) where Subject : CustomStringConvertible
init<Subject>(describing instance: Subject) where Subject : CustomStringConvertible, Subject : TextOutputStreamable
init<Subject>(describing instance: Subject)
the last the generic param Subject is totally unqualified. So why first three? Why just the last one not suffice?
I'm coming from this tweet on using custom string interpolation to print out optional value. His appendInterpolation(...) only handle CustomStringConvertible type. I look up String.init(describing:) and saw there are four overloads. I thought maybe there needs to be four overload of appendInteerpolation(...) to handle all the types that String.init(describing:) can? But then I saw the last String.init(describing:) is just <Subject> which prompted me to wonder why the other three overloads exist at all? Why not just this last one with <Subject> only?
Anyway, back to the custom String.StringInterpolation, I tried just this:
-- just one appendInterpolation<Subject> seems to work just fine. No need for those other overloads. Is this enough here? Is this sufficient? Or I need all four?
let any: Any = "Help"
let str = String(describing: any)
print(str) // print out "Help"
var anyOptional: Any? = nil
print("anyOptional is \(anyOptional, default: "url is nil")") // print out "anyOptional is url is nil"
I mean a custom type that uses CustomStringConvertible.description in a “non-traditional” way. I wonder if the initializer will use it. Something like this:
struct Foo: CustomStringConvertible, TextOutputStreamable {
func write<Target>(to target: inout Target) where Target : TextOutputStream {
target.write("Text Output Streamable")
}
var description: String { "Custom String Convertible" }
}
// Custom String Convertible
print(String(describing: Foo()))
// Text Output Streamable
print(String(describing: Foo() as Any))
print(String(describing: Foo() as CustomStringConvertible))
print(String(describing: Foo() as TextOutputStreamable))
print(String(describing: Foo() as CustomStringConvertible & TextOutputStreamable))
I think it all comes down to efficiency and correctness. The source code for the initializers show that they have different implementations for different Subject.
Let me label the 4 initializers, so they're more wieldy:
TextOutputStreamable and CustomStringConvertible are 2 protocols that each allows the user to provide a description for an instance of a type. Initializers 1 and 2 take advantage of their existence, and use the user-defined value as the instance's description. 1 and 2 are implemented differently, because the "description" part of TextOutputStreamable and CustomStringConvertible are implemented differently: TextOutputStreamable uses write(to:) and CustomStringConvertible uses description.
Because initializers 1 and 2 are implemented differently, 3 becomes necessary for the compiler to know what to do when Subject conforms to both CustomStringConvertible and TextOutputStreamable.
4 (the most general case?) uses reflection to create a description of an instance, regardless of its type.
Initializers 1, 2. and 3 exist when 4 already works for all Subjects, because it's more efficient (and correct?) to use what the user has provided, instead of finding out an instance's description through reflection.
So I should also have four appendInterpolation<Subject>(...) where ... (the ones I commented out) so it cal call the matching the String.init(describing:) then, yes? Even though the most generic one should work.
I'm not sure if String.init(describing:) in your appendInterpolation in the top of this thread receives dynamic dispatch. If it does, then your current appendInterpolation already takes care of handling different String.init(describing:). If it doesn't, then you'll need to overload your appendInterpolation to take more refined implementations of String.init(describing:).
That is, it’s identical to fetching the .description property of the value, and is likely to get inlined to just that.
Whereas the the unconstrained version does a whole bunch of stuff. First it creates an empty string. Then it calls another function to write the value to that string. That speculatively tries to cast the value to various types (generating metadata and other detritus along the way) until it finds the right type. If it doesn’t, it falls back to reflection.
Ah right, totally forgot that a protocol doesn't conform to itself. Sorry for a confusion (from my code above). You need to create a new type that only conforms to CSC, or avoid existential. Something like this:
Because CustomStringConvertible? is Optional<CustomStringConvertible>, which is a different type from CustomStringConvertible, and does not conform to CustomStringConvertible.
Optional<CustomStringConvertible> doesn't conform to TextOutputStreamable either.
So the only choice left is the 4th appendInterpolation.
Interesting. I didn't know that. It seems very reasonable that it doesn't conform to itself, to think about it. Since you can't really find a default implementation for description.
Hard to understand, but the compile error message makes this clear for me:
func funkyfunk<T: CustomStringConvertible>(_ v: T) {
print(v.description)
}
funkyfunk(Foo() as CustomStringConvertible) // <== compile error here
**error: value of protocol type 'CustomStringConvertible' cannot conform to 'CustomStringConvertible'; only struct/enum/class types can conform to protocols**
**funkyfunk(Foo() as CustomStringConvertible)**
**note: required by global function 'funkyfunk' where 'T' = 'CustomStringConvertible'**
Rephrasing the first error message: Protocols cannot comform to protocols. Only struct/enum/class types can conform to protocols.
So with just a protocol type, Swift cannot call the specific appendInterpolation<Subject:...>(...), so it just call the unqualified generic one (the last one). There is no way to make string interpolation to call the specific one with just a protocol with common overloading.
But should work with specific call signature:
"\(csc: s1, default: "s1 is nil")"
(never mind, do not work with just protocol, still the same reason: protocol cannot conform to protocol)
I think it's clear now for me.
Also, all four appendInterpolation<Subject:...>(...) are needed in order to get the right specialized String.init(describing:)