Default Generic Arguments

Oh, it's precisely my confidence that a good error message can be devised
which makes me ponder whether "prefer user" is the ideal rule. Having a
stricter rule isn't necessarily bad if the error message makes it easy to
remedy.

In your example, "prefer user" would object at the line where you make your
Something. I think that makes for a much cleaner error. By contrast, DWIM
necessitates the acrobatics you show above, where the compiler will have to
keep track of a defaulted type for each variable as long as it's in scope
and propose remote fix-its at the declaration site based on how it's later
used. Now what happens if there's an action() that takes only
Something<Int> arguments and an action2() that takes only Something<Int64>
arguments? Will you have an alternating cycle of fix-its that don't fix the
problem?

···

On Fri, Jan 27, 2017 at 18:07 Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 01:30, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

Cool, thanks--that makes sense.

Personally, although DWIM is appealing, I think if we are to go all-out on
your stance that "adding a default to an existing type parameter should be
a strict source-breaking change," then "prefer user" is the one rule that
maximally clarifies the scenario. With that rule, in the evolution
scenarios that I brought up, either the user-specified default and the
inferred literal type line up perfectly or it is guaranteed to be
source-breaking. IMO, that consistency would bring more clarity than DWIM,
which might prompt a user to be confused why sometimes the compiler "gets
it" and other times it doesn’t.

I’m not sure, I think it will be easy enough for users to figure out where
the problem is because it will create a type-mismatch.
When type mismatches occur, the only place to look is the variable
definition, because that is where the type is defined.

This is such a narrow case that I’m sure we can provide good diagnostics
for it. The pattern could be:

- A generic parameter mismatch (i.e. trying to use a value of type
MyType<X> where type MyType<Y> is expected), and
- X and Y are both {Whatever}LiteralConvertible, and
- X is the default type bound to that parameter, and
- the value was initialised using a {Whatever} literal, where an instance
of the parameter was expected

In that case, we could introduce a simple fix-it: replacing one of the
literal values with "(literal as Y)”

for example:

struct Something<T=Int64> { let value: T }
func action(_: Something<Int>) { … } // Expects a specific kind of
Something<T>

let myThing = Something(value: 42) // Fix-it: Did you
mean ‘Something(value: 42 as Int)’?
action(myThing) // Error: No overload for ‘action’
which takes a Something<Int64>.

I updated the proposal with the things we discussed so far. Have to do some
more polishing, but feel free to throw your critique of what I have so far.

···

On Sat, Jan 28, 2017 at 1:32 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Oh, it's precisely my confidence that a good error message can be devised
which makes me ponder whether "prefer user" is the ideal rule. Having a
stricter rule isn't necessarily bad if the error message makes it easy to
remedy.

In your example, "prefer user" would object at the line where you make
your Something. I think that makes for a much cleaner error. By contrast,
DWIM necessitates the acrobatics you show above, where the compiler will
have to keep track of a defaulted type for each variable as long as it's in
scope and propose remote fix-its at the declaration site based on how it's
later used. Now what happens if there's an action() that takes only
Something<Int> arguments and an action2() that takes only Something<Int64>
arguments? Will you have an alternating cycle of fix-its that don't fix the
problem?

On Fri, Jan 27, 2017 at 18:07 Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 01:30, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:

Cool, thanks--that makes sense.

Personally, although DWIM is appealing, I think if we are to go all-out
on your stance that "adding a default to an existing type parameter should
be a strict source-breaking change," then "prefer user" is the one rule
that maximally clarifies the scenario. With that rule, in the evolution
scenarios that I brought up, either the user-specified default and the
inferred literal type line up perfectly or it is guaranteed to be
source-breaking. IMO, that consistency would bring more clarity than DWIM,
which might prompt a user to be confused why sometimes the compiler "gets
it" and other times it doesn’t.

I’m not sure, I think it will be easy enough for users to figure out
where the problem is because it will create a type-mismatch.
When type mismatches occur, the only place to look is the variable
definition, because that is where the type is defined.

This is such a narrow case that I’m sure we can provide good diagnostics
for it. The pattern could be:

- A generic parameter mismatch (i.e. trying to use a value of type
MyType<X> where type MyType<Y> is expected), and
- X and Y are both {Whatever}LiteralConvertible, and
- X is the default type bound to that parameter, and
- the value was initialised using a {Whatever} literal, where an instance
of the parameter was expected

In that case, we could introduce a simple fix-it: replacing one of the
literal values with "(literal as Y)”

for example:

struct Something<T=Int64> { let value: T }
func action(_: Something<Int>) { … } // Expects a specific kind of
Something<T>

let myThing = Something(value: 42) // Fix-it: Did you
mean ‘Something(value: 42 as Int)’?
action(myThing) // Error: No overload for ‘action’
which takes a Something<Int64>.

I have concerns about these revisions. It seems you've entirely rejected
all the options that Alexis has laid out very clearly, and instead you've
come up with your own rules which are backwards-incompatible with Swift 3.

For instance, the rule "No type inference will happen in type declarations"
is source-breaking, because type inference currently happens in type
declarations. You would break every instance of `let foo: Optional = 42`.

I would urge you to incorporate Alexis's very clear analysis and then adopt
one of the options he laid out, i.e., either "prefer user" or "do what I
mean." Alternatively, if you like none of his options, I believe that
requiring `<>` to be appended for entirely default arguments would avoid
the issue altogether. Or, if you don't even want to require that, you can
push the whole problem down the road by specifying that, for now, the first
generic type cannot have a default. We can then relax the rules later.

···

On Tue, Jan 31, 2017 at 3:15 PM, Srđan Rašić <srdan.rasic@gmail.com> wrote:

I updated the proposal with the things we discussed so far. Have to do
some more polishing, but feel free to throw your critique of what I have so
far.

On Sat, Jan 28, 2017 at 1:32 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Oh, it's precisely my confidence that a good error message can be devised
which makes me ponder whether "prefer user" is the ideal rule. Having a
stricter rule isn't necessarily bad if the error message makes it easy to
remedy.

In your example, "prefer user" would object at the line where you make
your Something. I think that makes for a much cleaner error. By contrast,
DWIM necessitates the acrobatics you show above, where the compiler will
have to keep track of a defaulted type for each variable as long as it's in
scope and propose remote fix-its at the declaration site based on how it's
later used. Now what happens if there's an action() that takes only
Something<Int> arguments and an action2() that takes only Something<Int64>
arguments? Will you have an alternating cycle of fix-its that don't fix the
problem?

On Fri, Jan 27, 2017 at 18:07 Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 01:30, Xiaodi Wu via swift-evolution < >>> swift-evolution@swift.org> wrote:

Cool, thanks--that makes sense.

Personally, although DWIM is appealing, I think if we are to go all-out
on your stance that "adding a default to an existing type parameter should
be a strict source-breaking change," then "prefer user" is the one rule
that maximally clarifies the scenario. With that rule, in the evolution
scenarios that I brought up, either the user-specified default and the
inferred literal type line up perfectly or it is guaranteed to be
source-breaking. IMO, that consistency would bring more clarity than DWIM,
which might prompt a user to be confused why sometimes the compiler "gets
it" and other times it doesn’t.

I’m not sure, I think it will be easy enough for users to figure out
where the problem is because it will create a type-mismatch.
When type mismatches occur, the only place to look is the variable
definition, because that is where the type is defined.

This is such a narrow case that I’m sure we can provide good diagnostics
for it. The pattern could be:

- A generic parameter mismatch (i.e. trying to use a value of type
MyType<X> where type MyType<Y> is expected), and
- X and Y are both {Whatever}LiteralConvertible, and
- X is the default type bound to that parameter, and
- the value was initialised using a {Whatever} literal, where an
instance of the parameter was expected

In that case, we could introduce a simple fix-it: replacing one of the
literal values with "(literal as Y)”

for example:

struct Something<T=Int64> { let value: T }
func action(_: Something<Int>) { … } // Expects a specific kind of
Something<T>

let myThing = Something(value: 42) // Fix-it: Did you
mean ‘Something(value: 42 as Int)’?
action(myThing) // Error: No overload for ‘action’
which takes a Something<Int64>.

Hah, did I really do that :( I completely missed the fact that `let foo:
Optional = 42` is supported at the moment. I've been programming in Swift
in day zero, but never really used such syntax.

I'll update the PR, thanks for your feedback.

···

ons. 1. feb. 2017 kl. 00.05 skrev Xiaodi Wu <xiaodi.wu@gmail.com>:

I have concerns about these revisions. It seems you've entirely rejected
all the options that Alexis has laid out very clearly, and instead you've
come up with your own rules which are backwards-incompatible with Swift 3.

For instance, the rule "No type inference will happen in type
declarations" is source-breaking, because type inference currently happens
in type declarations. You would break every instance of `let foo: Optional
= 42`.

I would urge you to incorporate Alexis's very clear analysis and then
adopt one of the options he laid out, i.e., either "prefer user" or "do
what I mean." Alternatively, if you like none of his options, I believe
that requiring `<>` to be appended for entirely default arguments would
avoid the issue altogether. Or, if you don't even want to require that, you
can push the whole problem down the road by specifying that, for now, the
first generic type cannot have a default. We can then relax the rules later.

On Tue, Jan 31, 2017 at 3:15 PM, Srđan Rašić <srdan.rasic@gmail.com> > wrote:

I updated the proposal with the things we discussed so far. Have to do
some more polishing, but feel free to throw your critique of what I have so
far.

On Sat, Jan 28, 2017 at 1:32 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Oh, it's precisely my confidence that a good error message can be devised
which makes me ponder whether "prefer user" is the ideal rule. Having a
stricter rule isn't necessarily bad if the error message makes it easy to
remedy.

In your example, "prefer user" would object at the line where you make
your Something. I think that makes for a much cleaner error. By contrast,
DWIM necessitates the acrobatics you show above, where the compiler will
have to keep track of a defaulted type for each variable as long as it's in
scope and propose remote fix-its at the declaration site based on how it's
later used. Now what happens if there's an action() that takes only
Something<Int> arguments and an action2() that takes only Something<Int64>
arguments? Will you have an alternating cycle of fix-its that don't fix the
problem?

On Fri, Jan 27, 2017 at 18:07 Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 01:30, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

Cool, thanks--that makes sense.

Personally, although DWIM is appealing, I think if we are to go all-out on
your stance that "adding a default to an existing type parameter should be
a strict source-breaking change," then "prefer user" is the one rule that
maximally clarifies the scenario. With that rule, in the evolution
scenarios that I brought up, either the user-specified default and the
inferred literal type line up perfectly or it is guaranteed to be
source-breaking. IMO, that consistency would bring more clarity than DWIM,
which might prompt a user to be confused why sometimes the compiler "gets
it" and other times it doesn’t.

I’m not sure, I think it will be easy enough for users to figure out where
the problem is because it will create a type-mismatch.
When type mismatches occur, the only place to look is the variable
definition, because that is where the type is defined.

This is such a narrow case that I’m sure we can provide good diagnostics
for it. The pattern could be:

- A generic parameter mismatch (i.e. trying to use a value of type
MyType<X> where type MyType<Y> is expected), and
- X and Y are both {Whatever}LiteralConvertible, and
- X is the default type bound to that parameter, and
- the value was initialised using a {Whatever} literal, where an instance
of the parameter was expected

In that case, we could introduce a simple fix-it: replacing one of the
literal values with "(literal as Y)”

for example:

struct Something<T=Int64> { let value: T }
func action(_: Something<Int>) { … } // Expects a specific kind of
Something<T>

let myThing = Something(value: 42) // Fix-it: Did you
mean ‘Something(value: 42 as Int)’?
action(myThing) // Error: No overload for ‘action’
which takes a Something<Int64>.

The proposal has been refactored to include all the things we have
discussed here. Thanks everyone for your feedback. If I've missed
something, please shout.

···

On Wed, Feb 1, 2017 at 7:35 AM, Srđan Rašić <srdan.rasic@gmail.com> wrote:

Hah, did I really do that :( I completely missed the fact that `let foo:
Optional = 42` is supported at the moment. I've been programming in Swift
in day zero, but never really used such syntax.

I'll update the PR, thanks for your feedback.

ons. 1. feb. 2017 kl. 00.05 skrev Xiaodi Wu <xiaodi.wu@gmail.com>:

I have concerns about these revisions. It seems you've entirely rejected
all the options that Alexis has laid out very clearly, and instead you've
come up with your own rules which are backwards-incompatible with Swift 3.

For instance, the rule "No type inference will happen in type
declarations" is source-breaking, because type inference currently happens
in type declarations. You would break every instance of `let foo: Optional
= 42`.

I would urge you to incorporate Alexis's very clear analysis and then
adopt one of the options he laid out, i.e., either "prefer user" or "do
what I mean." Alternatively, if you like none of his options, I believe
that requiring `<>` to be appended for entirely default arguments would
avoid the issue altogether. Or, if you don't even want to require that, you
can push the whole problem down the road by specifying that, for now, the
first generic type cannot have a default. We can then relax the rules later.

On Tue, Jan 31, 2017 at 3:15 PM, Srđan Rašić <srdan.rasic@gmail.com> >> wrote:

I updated the proposal with the things we discussed so far. Have to do
some more polishing, but feel free to throw your critique of what I have so
far.

On Sat, Jan 28, 2017 at 1:32 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Oh, it's precisely my confidence that a good error message can be devised
which makes me ponder whether "prefer user" is the ideal rule. Having a
stricter rule isn't necessarily bad if the error message makes it easy to
remedy.

In your example, "prefer user" would object at the line where you make
your Something. I think that makes for a much cleaner error. By contrast,
DWIM necessitates the acrobatics you show above, where the compiler will
have to keep track of a defaulted type for each variable as long as it's in
scope and propose remote fix-its at the declaration site based on how it's
later used. Now what happens if there's an action() that takes only
Something<Int> arguments and an action2() that takes only Something<Int64>
arguments? Will you have an alternating cycle of fix-its that don't fix the
problem?

On Fri, Jan 27, 2017 at 18:07 Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 01:30, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:

Cool, thanks--that makes sense.

Personally, although DWIM is appealing, I think if we are to go all-out
on your stance that "adding a default to an existing type parameter should
be a strict source-breaking change," then "prefer user" is the one rule
that maximally clarifies the scenario. With that rule, in the evolution
scenarios that I brought up, either the user-specified default and the
inferred literal type line up perfectly or it is guaranteed to be
source-breaking. IMO, that consistency would bring more clarity than DWIM,
which might prompt a user to be confused why sometimes the compiler "gets
it" and other times it doesn’t.

I’m not sure, I think it will be easy enough for users to figure out
where the problem is because it will create a type-mismatch.
When type mismatches occur, the only place to look is the variable
definition, because that is where the type is defined.

This is such a narrow case that I’m sure we can provide good diagnostics
for it. The pattern could be:

- A generic parameter mismatch (i.e. trying to use a value of type
MyType<X> where type MyType<Y> is expected), and
- X and Y are both {Whatever}LiteralConvertible, and
- X is the default type bound to that parameter, and
- the value was initialised using a {Whatever} literal, where an instance
of the parameter was expected

In that case, we could introduce a simple fix-it: replacing one of the
literal values with "(literal as Y)”

for example:

struct Something<T=Int64> { let value: T }
func action(_: Something<Int>) { … } // Expects a specific kind of
Something<T>

let myThing = Something(value: 42) // Fix-it: Did you
mean ‘Something(value: 42 as Int)’?
action(myThing) // Error: No overload for ‘action’
which takes a Something<Int64>.