[Pitch] Remove type inference for associated types

Do these associated types have meaningful defaults? We’re not talking about eliminating the ability to have default types for associated types, e.g.,

  public protocol ResourceController {
    associatedtype CreateInput
    associatedtype UpdateInput = CreateInput

    associatedtype ListOutput
    associatedtype CreateOutput = ListOutput
    associatedtype DetailOutput = ListOutput
    associatedtype UpdateOutput = ListOutput

    associatedtype DetailID
    associatedtype UpdateID = DetailID
    associatedtype DestroyID = DetailID
  }

which might reduce the common case for conforming to the protocol to be, e.g.,

  extension TodoController : ResourceController {
    public typealias CreateInput = Todo
    public typealias ListOutput = Todo
    public typealias DetailID = String
  }

  - Doug

···

On Jun 28, 2016, at 9:45 PM, Paulo Faria <paulo@zewo.io> wrote:

On Jun 28, 2016, at 3:34 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Finally, I am very concerned that there are protocols such as Collection,
with many inferrable associated types, and that conforming to these
protocols could become *much* uglier.

Unfortunately I have a specific use case in which this argument would be very strong.

Basically this:

extension TodoController : ResourceController {}

Would have to become this:

extension TodoController : ResourceController {
    public typealias CreateInput = Todo
    public typealias UpdateInput = Todo

    public typealias ListOutput = Todo
    public typealias CreateOutput = Todo
    public typealias DetailOutput = Todo
    public typealias UpdateOutput = Todo

    public typealias DetailID = String
    public typealias UpdateID = String
    public typealias DestroyID = String
}

I could reduce the amount of associated types but this would reduce the flexibility of the protocol by a huge factor and would make it much less powerful. I’m very torn about this because I do want generics to get better. Specifically I’m looking forward to conditional conformances. But this would be a too high cost imho. I know this is just one example. But maybe there are more examples like this out there. I have to admit this one really got to me. :(

Unfortunately no. The whole purpose of having so many associated types is that the user can simply chose the types he wants to use as the input and output of each function required in the protocol.

public protocol ResourceController {
    associatedtype CreateInput: StructuredDataInitializable
    associatedtype UpdateInput: StructuredDataInitializable

    associatedtype ListOutput: StructuredDataFallibleRepresentable
    associatedtype CreateOutput: StructuredDataFallibleRepresentable
    associatedtype DetailOutput: StructuredDataFallibleRepresentable
    associatedtype UpdateOutput: StructuredDataFallibleRepresentable

    associatedtype DetailID: PathParameterInitializable
    associatedtype UpdateID: PathParameterInitializable
    associatedtype DestroyID: PathParameterInitializable

    func list() throws -> [ListOutput]
    func create(element: CreateInput) throws -> CreateOutput
    func detail(id: DetailID) throws -> DetailOutput
    func update(id: UpdateID, element: UpdateInput) throws -> UpdateOutput
    func destroy(id: DestroyID) throws
}

···

On Jun 29, 2016, at 1:51 AM, Douglas Gregor <dgregor@apple.com> wrote:

Do these associated types have meaningful defaults? We’re not talking about eliminating the ability to have default types for associated types, e.g.,

  public protocol ResourceController {
    associatedtype CreateInput
    associatedtype UpdateInput = CreateInput

    associatedtype ListOutput
    associatedtype CreateOutput = ListOutput
    associatedtype DetailOutput = ListOutput
    associatedtype UpdateOutput = ListOutput

    associatedtype DetailID
    associatedtype UpdateID = DetailID
    associatedtype DestroyID = DetailID
  }

which might reduce the common case for conforming to the protocol to be, e.g.,

  extension TodoController : ResourceController {
    public typealias CreateInput = Todo
    public typealias ListOutput = Todo
    public typealias DetailID = String
  }

  - Doug

But yeah! I guess that could help In the case the type is the same! Thanks for the tip.

···

On Jun 29, 2016, at 1:51 AM, Douglas Gregor <dgregor@apple.com> wrote:

which might reduce the common case for conforming to the protocol to be, e.g.,

Just discovered another horrible implication of type inference removal for the same use case. :OOO

This is the full code for the protocol

public protocol ResourceController {
    associatedtype DetailID: PathParameterInitializable = UpdateID
    associatedtype UpdateID: PathParameterInitializable
    associatedtype DestroyID: PathParameterInitializable = UpdateID

    associatedtype CreateInput: StructuredDataInitializable = UpdateInput
    associatedtype UpdateInput: StructuredDataInitializable

    associatedtype ListOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype CreateOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype DetailOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype UpdateOutput: StructuredDataFallibleRepresentable

    func list() throws -> [ListOutput]
    func create(element: CreateInput) throws -> CreateOutput
    func detail(id: DetailID) throws -> DetailOutput
    func update(id: UpdateID, element: UpdateInput) throws -> UpdateOutput
    func destroy(id: DestroyID) throws
}

extension ResourceController {
    public func list() throws -> [ListOutput] {
        throw ClientError.notFound
    }

    public func create(element: CreateInput) throws -> CreateOutput {
        throw ClientError.notFound
    }

    public func detail(id: DetailID) throws -> DetailOutput {
        throw ClientError.notFound
    }

    public func update(id: UpdateID, element: UpdateInput) throws -> UpdateOutput {
        throw ClientError.notFound
    }

    public func destroy(id: DestroyID) throws {
        throw ClientError.notFound
    }
}

Suppose we have an implementation like this:

public struct TodoController : ResourceController {
    public typealias CreateInput = NotTodo
    public typealias UpdateInput = NotTodo

    public typealias ListOutput = NotTodo
    public typealias CreateOutput = NotTodo
    public typealias DetailOutput = NotTodo
    public typealias UpdateOutput = NotTodo

    public typealias DetailID = NotString
    public typealias UpdateID = NotString
    public typealias DestroyID = NotString

    public func list() throws -> [Todo] {
        ...
    }

    public func create(element todo: Todo) throws -> Todo {
        ...
    }

    public func detail(id: String) throws -> Todo {
        ...
    }

    public func update(id: String, element todo: Todo) throws -> Todo {
        ...
    }

    public func destroy(id: String) throws {
        ...
    }
}

Notice that the typealiases and the function declarations don’t match. But the compiler doesn’t complain because the protocol has default implementations in the protocol extension. This means that if the person implementing the protocol doesn’t make sure the types match exactly, there’s gonna be unexpected behaviour. Which I’m sure he/she will take quite some time to figure out. :(

···

On Jun 29, 2016, at 1:51 AM, Douglas Gregor <dgregor@apple.com> wrote:

which might reduce the common case for conforming to the protocol to be, e.g.,

I'm not sure this is a problem. Once you bind the associated types explicitly, the requirements using those associated types need to match the bound types otherwise the type checker will emit an error. If you have default associated types and default implementations, but then bind the associated types differently and do not update your type-specific implementations to use those other types, you will get an error message. This is how explicitly specifying associated types works today.

Austin

···

On Jun 28, 2016, at 10:17 PM, Paulo Faria via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 29, 2016, at 1:51 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

which might reduce the common case for conforming to the protocol to be, e.g.,

Just discovered another horrible implication of type inference removal for the same use case. :OOO

This is the full code for the protocol

public protocol ResourceController {
    associatedtype DetailID: PathParameterInitializable = UpdateID
    associatedtype UpdateID: PathParameterInitializable
    associatedtype DestroyID: PathParameterInitializable = UpdateID

    associatedtype CreateInput: StructuredDataInitializable = UpdateInput
    associatedtype UpdateInput: StructuredDataInitializable

    associatedtype ListOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype CreateOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype DetailOutput: StructuredDataFallibleRepresentable = UpdateOutput
    associatedtype UpdateOutput: StructuredDataFallibleRepresentable

    func list() throws -> [ListOutput]
    func create(element: CreateInput) throws -> CreateOutput
    func detail(id: DetailID) throws -> DetailOutput
    func update(id: UpdateID, element: UpdateInput) throws -> UpdateOutput
    func destroy(id: DestroyID) throws
}

extension ResourceController {
    public func list() throws -> [ListOutput] {
        throw ClientError.notFound
    }

    public func create(element: CreateInput) throws -> CreateOutput {
        throw ClientError.notFound
    }

    public func detail(id: DetailID) throws -> DetailOutput {
        throw ClientError.notFound
    }

    public func update(id: UpdateID, element: UpdateInput) throws -> UpdateOutput {
        throw ClientError.notFound
    }

    public func destroy(id: DestroyID) throws {
        throw ClientError.notFound
    }
}

Suppose we have an implementation like this:

public struct TodoController : ResourceController {
    public typealias CreateInput = NotTodo
    public typealias UpdateInput = NotTodo

    public typealias ListOutput = NotTodo
    public typealias CreateOutput = NotTodo
    public typealias DetailOutput = NotTodo
    public typealias UpdateOutput = NotTodo

    public typealias DetailID = NotString
    public typealias UpdateID = NotString
    public typealias DestroyID = NotString

    public func list() throws -> [Todo] {
        ...
    }

    public func create(element todo: Todo) throws -> Todo {
        ...
    }

    public func detail(id: String) throws -> Todo {
        ...
    }

    public func update(id: String, element todo: Todo) throws -> Todo {
        ...
    }

    public func destroy(id: String) throws {
        ...
    }
}

Notice that the typealiases and the function declarations don’t match. But the compiler doesn’t complain because the protocol has default implementations in the protocol extension. This means that if the person implementing the protocol doesn’t make sure the types match exactly, there’s gonna be unexpected behaviour. Which I’m sure he/she will take quite some time to figure out. :(

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The problem is that it doesn’t give me any error message. I tested it.

···

On Jun 29, 2016, at 2:28 AM, Austin Zheng <austinzheng@gmail.com> wrote:

I'm not sure this is a problem. Once you bind the associated types explicitly, the requirements using those associated types need to match the bound types otherwise the type checker will emit an error. If you have default associated types and default implementations, but then bind the associated types differently and do not update your type-specific implementations to use those other types, you will get an error message. This is how explicitly specifying associated types works today.

Austin

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

···

On Jun 28, 2016, at 10:47 PM, Paulo Faria <paulo@zewo.io> wrote:

On Jun 29, 2016, at 2:28 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

I'm not sure this is a problem. Once you bind the associated types explicitly, the requirements using those associated types need to match the bound types otherwise the type checker will emit an error. If you have default associated types and default implementations, but then bind the associated types differently and do not update your type-specific implementations to use those other types, you will get an error message. This is how explicitly specifying associated types works today.

Austin

The problem is that it doesn’t give me any error message. I tested it.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

···

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

···

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com> wrote:

Yeah. Well, if we get the error message. It’s better than nothing. I don’t know the specifics about compilers and rules and what not. But it seems to me it would be opening an exception just for that specific case. And that might be something that would make the compiler rules more complex. So if we don’t get the message, we get more consistency in the rules, but on the other hand we make it easier for people to accidentally get unexpected behaviour.

···

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com> wrote:

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

Actually, the more I think about it I get the conclusion that It really shouldn’t be an error. Otherwise this would mean that you wouldn’t be able to use the default implementation and have a function with the same name but returning another type. And if we can’t get an error; We’re just making it easier for the bugs by forcing one to keep the typealias and his own implementation in sync. With inference this wouldn’t exist. This is a really hard one. Looks like the decision is already made. If this is really the case; All I can say is that I really hope this comes back in Swift 4.

···

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com> wrote:

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

For reference, my implementation makes it a warning, not an error, and you can suppress the warning by moving the similar-but-not-selected declaration to a different extension.

  - Doug

···

On Jun 28, 2016, at 11:36 PM, Paulo Faria <paulo@zewo.io> wrote:

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

Actually, the more I think about it I get the conclusion that It really shouldn’t be an error. Otherwise this would mean that you wouldn’t be able to use the default implementation and have a function with the same name but returning another type. And if we can’t get an error; We’re just making it easier for the bugs by forcing one to keep the typealias and his own implementation in sync. With inference this wouldn’t exist. This is a really hard one. Looks like the decision is already made. If this is really the case; All I can say is that I really hope this comes back in Swift 4.

Your implementation is the current one?

···

On Jun 29, 2016, at 3:40 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jun 28, 2016, at 11:36 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

Actually, the more I think about it I get the conclusion that It really shouldn’t be an error. Otherwise this would mean that you wouldn’t be able to use the default implementation and have a function with the same name but returning another type. And if we can’t get an error; We’re just making it easier for the bugs by forcing one to keep the typealias and his own implementation in sync. With inference this wouldn’t exist. This is a really hard one. Looks like the decision is already made. If this is really the case; All I can say is that I really hope this comes back in Swift 4.

For reference, my implementation makes it a warning, not an error, and you can suppress the warning by moving the similar-but-not-selected declaration to a different extension.

  - Doug

No, I never committed it because at the time we were still talking about an “implements” keyword (or similar) to indicate that a particular declaration was intended to satisfy a protocol requirement.

  - Doug

···

On Jun 28, 2016, at 11:43 PM, Paulo Faria <paulo@zewo.io> wrote:

On Jun 29, 2016, at 3:40 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Jun 28, 2016, at 11:36 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

Actually, the more I think about it I get the conclusion that It really shouldn’t be an error. Otherwise this would mean that you wouldn’t be able to use the default implementation and have a function with the same name but returning another type. And if we can’t get an error; We’re just making it easier for the bugs by forcing one to keep the typealias and his own implementation in sync. With inference this wouldn’t exist. This is a really hard one. Looks like the decision is already made. If this is really the case; All I can say is that I really hope this comes back in Swift 4.

For reference, my implementation makes it a warning, not an error, and you can suppress the warning by moving the similar-but-not-selected declaration to a different extension.

  - Doug

Your implementation is the current one?

Awesome! I hope the final implementation has the warning.

···

On Jun 29, 2016, at 3:45 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jun 28, 2016, at 11:43 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 3:40 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Jun 28, 2016, at 11:36 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:54 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

Sent from my iPhone

On Jun 28, 2016, at 10:51 PM, Paulo Faria <paulo@zewo.io <mailto:paulo@zewo.io>> wrote:

On Jun 29, 2016, at 2:48 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

If you tested it, there's a problem with the current behavior independent of associated type inference, and it should be fixed whether or not the proposal is accepted.

I don’t think it should be an error. Normally we can have any number of functions with the same name but different return types. By failing in this specific case would be like saying that when you implement a protocol with an associated type you can only return what’s defined in the associated type. I don’t think that should be the behaviour we expect.

This seems very related to the near-miss checking I mentioned in my other reply to Austin.

  - Doug

Actually, the more I think about it I get the conclusion that It really shouldn’t be an error. Otherwise this would mean that you wouldn’t be able to use the default implementation and have a function with the same name but returning another type. And if we can’t get an error; We’re just making it easier for the bugs by forcing one to keep the typealias and his own implementation in sync. With inference this wouldn’t exist. This is a really hard one. Looks like the decision is already made. If this is really the case; All I can say is that I really hope this comes back in Swift 4.

For reference, my implementation makes it a warning, not an error, and you can suppress the warning by moving the similar-but-not-selected declaration to a different extension.

  - Doug

Your implementation is the current one?

No, I never committed it because at the time we were still talking about an “implements” keyword (or similar) to indicate that a particular declaration was intended to satisfy a protocol requirement.

  - Doug