[swift-evolution] Default Generic Arguments
Karl Wagner
razielim at gmail.com
Thu Jan 26 09:28:24 CST 2017
> On 26 Jan 2017, at 02:15, Xiaodi Wu via swift-evolution <swift-evolution at swift.org> wrote:
>
> Srdan, I'm afraid I don't understand your discussion. Can you simplify it for me by explaining your proposed solution in terms of Alexis's examples below?
>
> ```
> // Example 1: user supplied default is IntegerLiteralConvertible
>
> func foo<T=Int64>(t: T) { ... }
>
> foo(22)
> // ^
> // |
> // What type gets inferred here?
> ```
>
> I believe that it is essential that the answer here be `Int` and not `Int64`.
>
> My reasoning is: a user's code *must not* change because a library *adds* a default in a newer version. (As mentioned in several design docs, most recently the new ABI manifesto, defaults in Swift are safe to add without breaking source compatibility.)
>
> Here, if version 1 of a library has `func foo<T>(t: T) { ... }`, then `foo(22)` must infer `T` to be `Int`. That's just the rule in Swift, and it would be severely source-breaking to change that. Therefore, if version 2 of that library has `func foo<T=Int64>(t: T) { ... }`, then `foo(22)` must still infer `T` to be `Int`.
>
> Does your proposed solution have the same effect?
>
> ```
> // Example 2: user supplied default isn't IntegerLiteralConvertible
>
> func bar<T=Character>(t: T) { ... }
>
> bar(22)
> // ^
> // |
> // What type gets inferred here?
> ```
>
> By the same reasoning as above, this ought to be `Int`. What would the answer be in your proposed solution?
>
I fundamentally disagree. In this case, you’re treating “22” as having type Int, which is conceptually false. Literals do not have an inherent type - they are (ideally) abstract, arbitrary-precision “things” which can be transformed to a type in some context.
Let’s consider what a default binding for a generic parameter actually is and when it would be used by the compiler. It is a hint which gives the compiler context when it can’t infer one in any other way. Basically: if you pass in a String as parameter ’t’, the compiler has some context and knows that T must be String.self. It is only when the parameter value is representable as multiple types that the default would be consulted.
As Gankro mentioned WRT the Rust proposal, Int is not a “default” type for IntegerLiteralConvertible; it is a “fallback" to be used when the compiler cannot infer any other type because there is absolutely no context (e.g. “let myNum = 42”). In some other context, that literal may be transformed in to a different numerical type, a struct, enum, class or even an entire class hierarchy.
I believe that programmers would expect the literal in the first example to be an Int64. The function declaration gives the compiler context for the preferred type to use.
Ultimately, default bindings for generic parameters are syntax-level conveniences. There is no code which is impossible without them; they save you being explicit about certain types by allowing the library author to pick sensible/optimal values to use in case you have no specific requirement for a particular parameter. Therefore, it stands to reason that adding a default parameter, and feeding the compiler some context where there previously was none, would be a potentially source-breaking change. It does not, however, necessarily need to be a resilience-breaking change; we could store the defaults information in some kind of metadata and leave the original function symbols intact. The function/type is still as generic as it ever was, after all.
In your example, T is never returned from the function, so it makes no difference to anybody what type “T” actually gets inferred as. If it did, and they tried to store it as some particular type which is no longer the resolved type of T, it would be a compile error. Just like we have to do all the time in Swift, they would have to provide explicit context - for example, by writing “22 as Int”.
If the design documents disagree, we can change them. There is no such thing as “default types” in the language right now (only default values), so it is surprising that ABI documentation would attempt to impose constraints on whether or not they may be source-breaking. It is more important that we have the right design for Swift, rather than one which is consistent with some arbitrary restrictions defined before the feature even existed.
As for the second example, yes it should be Int. The context, which tells the compiler to prefer Character, is not meaningful to an integer literal. In that case, the zero-context “fallback” applied - i.e. Swift.Int.
- Karl
More information about the swift-evolution
mailing list