[Pitch] Simpler interpretation of a reference to a generic type with no arguments

Good to know. I absolutely agree that the gains to be had here wouldn't be worth a one-off hack.

If people are strongly (or even mildly) opposed to removing this rule, we can give some thought to a more general solution. I don’t feel very strongly about this proposal one way or another, it’s just a bit of ugly code in TypeCheckType.cpp that would be nice to remove, and a developer on Twitter recently noticed this behavior and found it surprising.

···

On Jun 23, 2016, at 1:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Jun 23, 2016 at 15:36 Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Jun 23, 2016, at 1:34 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Sorry, it's I who is saying things all wrong. I meant to ask, is it feasible to keep both behaviors but have #2 "win" over #1, instead of getting rid of behavior #1 entirely?

I suspect there might be some way, but I think it would have to be some kind of one-off hack, which is not in line with our long-term goal of making the type checker more maintainable and correct ‘by construction’.

On Thu, Jun 23, 2016 at 15:30 Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Jun 23, 2016, at 1:27 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

When you mention the difficulty of an alternative, is that to say that it's not feasible for the GenericBox in the last example to be resolved as GenericBox<T>? From an end-user point of view, that seems to be the most sensible behavior.

With my proposed change, GenericBox would be resolved as GenericBox<T> in the last example. Right now it fails to type check.

Here is an example that works right now, but would not work with my proposed change:

struct GenericBox<Contents> {
  // Currently Swift resolves this as ‘GenericBox<Contents>’
  // With the new rule, we cannot infer the parameter, because there’s no expression to infer it from
  func combine(other: GenericBox) {
    …
  }
}

Basically the meaning of ‘GenericBox’ right now depends on whether it appears inside its own definition or extension thereof, or not. The behavior when it appears elsewhere is more general — we infer the parameters from the surrounding expression, instead of assuming they’re equal to the context parameters.

This is a subtle change — definitely let me know if I’m not explaining it well.

Slava

On Thu, Jun 23, 2016 at 15:14 Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Simpler interpretation of a reference to a generic type with no arguments

Proposal: SE-9999 <https://github.com/slavapestov/swift-evolution/blob/silly-proposals/proposals/9999-simplify-unbound-generic-type.md&gt;
Author: Slava Pestov <https://github.com/slavapestov&gt;
Status: Awaiting review
Review manager: TBD
<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#introduction&gt;Introduction

This proposal cleans up the semantics of a reference to a generic type when no generic arguments are applied.

Swift-evolution thread: Discussion thread topic for that proposal <http://news.gmane.org/gmane.comp.lang.swift.evolution&gt;
<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#motivation&gt;Motivation

Right now, we allow a generic type to be referenced with no generic arguments applied in a handful of special cases. The two primary rules here are the following:

If the scope from which the reference is made is nested inside the definition of the type or an extension thereof, omitting generic arguments just means to implicitly apply the arguments from context.

For example,

struct GenericBox<Contents> {
  let contents: Contents

  // Equivalent to: func clone() -> GenericBox<Contents>
  func clone() -> GenericBox {
    return GenericBox(contents: contents)
  }
}

extension GenericBox {
  func print() {
    // Equivalent to: let cloned: GenericBox<Contents>
    let cloned: GenericBox = clone()
    print(cloned.contents)
  }
}
If the type is referenced from an unrelated scope, we attempt to infer the generic parameters.

For example,

func makeABox() -> GenericBox<Int> {
  // Equivalent to: GenericBox<Int>(contents: 123)
  return GenericBox(contents: 123)
}
The problem appears when the user expects the second behavior, but instead encounters the first. For example, the following does not type check:

extension GenericBox {

  func transform<T>(f: Contents -> T) -> GenericBox<T> {
    // We resolve 'GenericBox' as 'GenericBox<Contents>', rather than
    // inferring the type parameter
    return GenericBox(contents: f(contents))
  }
}
<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#proposed-solution&gt;Proposed solution

The proposed solution is to remove the first rule altogether. If the generic parameters cannot be inferred from context, they must be specified explicitly with the usual Type<Args...> syntax.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#detailed-design&gt;Detailed design

This really just involves removing an existing piece of logic from the type resolver code.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#impact-on-existing-code&gt;Impact on existing code

This will have a small impact on existing code that uses a pattern similar to the above.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#alternatives-considered&gt;Alternatives considered

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#status-quo&gt;Status quo

We could keep the current behavior, but one can argue it is not very useful, and adds a special case where one is not needed.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#more-complex-inference-of-generic-parameters&gt;More complex inference of generic parameters

We could attempt to unify the two rules for resolving a reference to a generic type with no arguments, however this presents theoretical difficulties with our constraint solver design. Even if it were easy to implement, it would increase type checking type by creating new possibilities to consider, with very little actual benefit.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Cool--no opposition here!

···

On Thu, Jun 23, 2016 at 15:43 Slava Pestov <spestov@apple.com> wrote:

On Jun 23, 2016, at 1:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Good to know. I absolutely agree that the gains to be had here wouldn't be
worth a one-off hack.

If people are strongly (or even mildly) opposed to removing this rule, we
can give some thought to a more general solution. I don’t feel very
strongly about this proposal one way or another, it’s just a bit of ugly
code in TypeCheckType.cpp that would be nice to remove, and a developer on
Twitter recently noticed this behavior and found it surprising.

On Thu, Jun 23, 2016 at 15:36 Slava Pestov <spestov@apple.com> wrote:

On Jun 23, 2016, at 1:34 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Sorry, it's I who is saying things all wrong. I meant to ask, is it
feasible to keep both behaviors but have #2 "win" over #1, instead of
getting rid of behavior #1 entirely?

I suspect there might be some way, but I think it would have to be some
kind of one-off hack, which is not in line with our long-term goal of
making the type checker more maintainable and correct ‘by construction’.

On Thu, Jun 23, 2016 at 15:30 Slava Pestov <spestov@apple.com> wrote:

On Jun 23, 2016, at 1:27 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

When you mention the difficulty of an alternative, is that to say that
it's not feasible for the GenericBox in the last example to be resolved as
GenericBox<T>? From an end-user point of view, that seems to be the most
sensible behavior.

With my proposed change, GenericBox would be resolved as GenericBox<T>
in the last example. Right now it fails to type check.

Here is an example that works right now, but would not work with my
proposed change:

struct GenericBox<Contents> {
// Currently Swift resolves this as ‘GenericBox<Contents>’
// With the new rule, we cannot infer the parameter, because there’s no
expression to infer it from
func combine(other: GenericBox) {

}
}

Basically the meaning of ‘GenericBox’ right now depends on whether it
appears inside its own definition or extension thereof, or not. The
behavior when it appears elsewhere is more general — we infer the
parameters from the surrounding expression, instead of assuming they’re
equal to the context parameters.

This is a subtle change — definitely let me know if I’m not explaining
it well.

Slava

On Thu, Jun 23, 2016 at 15:14 Slava Pestov via swift-evolution < >>> swift-evolution@swift.org> wrote:

Simpler interpretation of a reference to a generic type with no
arguments

   - Proposal: SE-9999
   <https://github.com/slavapestov/swift-evolution/blob/silly-proposals/proposals/9999-simplify-unbound-generic-type.md&gt;
   - Author: Slava Pestov <https://github.com/slavapestov&gt;
   - Status: Awaiting review
   - Review manager: TBD

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#introduction&gt;
Introduction

This proposal cleans up the semantics of a reference to a generic type
when no generic arguments are applied.

Swift-evolution thread: Discussion thread topic for that proposal
<http://news.gmane.org/gmane.comp.lang.swift.evolution&gt;

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#motivation&gt;
Motivation

Right now, we allow a generic type to be referenced with no generic
arguments applied in a handful of special cases. The two primary rules here
are the following:

   -

   If the scope from which the reference is made is nested inside the
   definition of the type or an extension thereof, omitting generic arguments
   just means to implicitly apply the arguments from context.

   For example,

struct GenericBox<Contents> {
  let contents: Contents

  // Equivalent to: func clone() -> GenericBox<Contents>
  func clone() -> GenericBox {
    return GenericBox(contents: contents)
  }
}
extension GenericBox {
  func print() {
    // Equivalent to: let cloned: GenericBox<Contents>
    let cloned: GenericBox = clone()
    print(cloned.contents)
  }
}

   -

   If the type is referenced from an unrelated scope, we attempt to
   infer the generic parameters.

   For example,

func makeABox() -> GenericBox<Int> {
  // Equivalent to: GenericBox<Int>(contents: 123)
  return GenericBox(contents: 123)
}

The problem appears when the user expects the second behavior, but
instead encounters the first. For example, the following does not type
check:

extension GenericBox {

  func transform<T>(f: Contents -> T) -> GenericBox<T> {
    // We resolve 'GenericBox' as 'GenericBox<Contents>', rather than
    // inferring the type parameter
    return GenericBox(contents: f(contents))
  }
}

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#proposed-solution&gt;Proposed
solution

The proposed solution is to remove the first rule altogether. If the
generic parameters cannot be inferred from context, they must be specified
explicitly with the usual Type<Args...> syntax.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#detailed-design&gt;Detailed
design

This really just involves removing an existing piece of logic from the
type resolver code.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#impact-on-existing-code&gt;Impact
on existing code

This will have a small impact on existing code that uses a pattern
similar to the above.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#alternatives-considered&gt;Alternatives
considered
<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#status-quo&gt;Status
quo

We could keep the current behavior, but one can argue it is not very
useful, and adds a special case where one is not needed.

<https://github.com/slavapestov/swift-evolution/tree/silly-proposals/proposals#more-complex-inference-of-generic-parameters&gt;More
complex inference of generic parameters
We could attempt to unify the two rules for resolving a reference to a
generic type with no arguments, however this presents theoretical
difficulties with our constraint solver design. Even if it were easy to
implement, it would increase type checking type by creating new
possibilities to consider, with very little actual benefit.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution