That's a good example Alexis. I do agree that generic arguments are inferred in a lot of cases, my point was that they should not be inferred in "type declarations". Not sure what's the right terminology here, but I mean following places:
(I) Variable/Constant declaration ``` let x: X ``` (II) Property declaration ``` struct T { let x: X } ``` (III) Function declaration ``` func a(x: X) -> X ``` (IV) Enumeration case declaration ``` enum E { case x(X) } ``` (V) Where clauses ``` extensions E where A == X {} ``` In those cases `X` should always mean `X<Int>` if it was defined as `struct X<T = Int>`. That's all my rule says. Sorry for not being clear in the last email :) As for the other cases, mostly those where an instance is created, inference should be applied. Let's go through your examples. Given struct BigInt: Integer { var storage: Array<Int> = [] } func process<T: BinaryInteger>(_ input: BigInt<T>) -> BigInt<T> { ... } what happens with `let val1 = process(BigInt())`? I think this is actually the same problem as what happens in case of `let x = BigInt()`. In such case my rule does not apply as we don't have full type declaration. In `let x = BigInt()` type is not defined at all, while in `func process<T: BinaryInteger>(_ input: BigInt<T>) -> BigInt<T> { ... }` type is explicitly weakened or "undefaulted" if you will. We should introduce new rule for such cases and allowing `Storage=Int` default to participate in such expressions would make sense. As you said, it also solves second example: let val2 = process(0). I guess this would be the problem we thought we were solving initially and in that case I think the solution should be what Doug suggested: if you can’t infer a particular type, fill in a default. Of course, if the default conflicts with the generic constraint, it would not be filled in and it would throw an error. For the sake of completeness, func fastProcess(_ input: BigInt<Int64>) -> BigInt<Int64> { ... } let val3 = fastProcess(BigInt()) would certainly infer the type from context as my rule does not apply to initializers. It would infer BigInt<Int64>. As for your last example, I guess we can't do anything about that and that's ok. On Wed, Jan 25, 2017 at 7:50 PM, Alexis <abeingess...@apple.com> wrote: > Yes, I agree with Xiaodi here. I don’t think this particular example is > particularly compelling. Especially because it’s not following the full > evolution of the APIs and usage, which is critical for understanding how > defaults should work. > > > Let's look at the evolution of an API and its consumers with the example > of a BigInt: > > > struct BigInt: Integer { > var storage: Array<Int> = [] > } > > > which a consumer is using like: > > > func process(_ input: BigInt) -> BigInt { ... } > let val1 = process(BigInt()) > let val2 = process(0) > > > Ok that's all fairly straightforward. Now we decide that BigInt should > expose its storage type for power-users: > > > struct BigInt<Storage: BinaryInteger = Int>: Integer { > var storage: Array<Storage> = [] > } > > > Let's make sure our consumer still works: > > > func process(_ input: BigInt) -> BigInt { ... } > let val1 = process(BigInt()) > let val2 = process(0) > > > Ok BigInt in process’s definition now means BigInt<Int>, so this still all > works fine. Perfect! > > > But then the developer of the process function catches wind of this new > power user feature, and wants to support it. > So they too become generic: > > > func process<T: BinaryInteger>(_ input: BigInt<T>) -> BigInt<T> { ... } > > > The usage sites are now more complicated, and whether they should compile > is unclear: > > > let val1 = process(BigInt()) > let val2 = process(0) > > > For val1 you can take a hard stance with your rule: BigInt() means > BigInt<Int>(), and that will work. But for val2 this rule doesn't work, > because no one has written BigInt unqualified. However if you say that the > `Storage=Int` default is allowed to participate in this expression, then we > can still find the old behaviour by defaulting to it when we discover > Storage is ambiguous. > > We can also consider another power-user function: > > > func fastProcess(_ input: BigInt<Int64>) -> BigInt<Int64> { ... } > let val3 = fastProcess(BigInt()) > > > Again, we must decide the interpretation of this. If we take the > interpretation that BigInt() has an inferred type, then the type checker > should discover that BigInt<Int64> is the correct result. If however we > take stance that BigInt() means BigInt<Int>(), then we'll get a type > checking error which our users will consider ridiculous: *of course* they > wanted a BigInt<Int64> here! > > We do however have the problem that this won’t work: > > > let temp = BigInt() > fastProcess(temp) // ERROR — expected BigInt<Int64>, found BigInt<Int> > > > But that’s just as true for normal ints: > > > let temp = 0 > takesAnInt64(temp) // ERROR — expected Int64, found Int > > > Such is the limit of Swift’s inference scheme. > >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution