[Swift 4.0] Conditional conformances via protocol extensions


(Manav Gabhawala) #1

In the generics manifesto
(https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md)
conditional conformances via protocol extensions is under unlikely.

The manifesto notes:

"This is an extremely powerful feature: is allows one to map the
abstractions of one domain into another domain (e.g., every Matrix is
a Graph). However, similar to private conformances, it puts a major
burden on the dynamic-casting runtime to chase down arbitrarily long
and potentially cyclic chains of conformances, which makes efficient
implementation nearly impossible."

Correct me if I’m completely wrong here but I think this is a super
useful feature and is worth talking about.
Firstly why its super useful if not immediately clear is cause you can
do neat things like:

extension Collection : Equatable where Element: Equatable

And you can even make protocols in a module that you did not write
conform to an arbitrary other protocol and give it a default
implementation.

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

I had a (perhaps naïve) implementation here:
https://github.com/apple/swift/pull/2233 and I think now would be a
great time to talk about such a feature.

Its possible I completely overlooked something but I would like a more
a detailed explanation about why this isn’t possible.

Regards,
Manav Gabhawala


(Brent Royal-Gordon) #2

My understanding—which may be incorrect, by the way—is that the issue is mainly with protocol extensions adding conformances, not specifically with those conformances being conditional, and that it specifically has to do with `is` and `as?` checks across module boundaries.

Suppose you have these declarations in module M:

  public protocol AProtocol {…}
  public protocol BProtocol: AProtocol {…}
  public protocol CProtocol {…}
  
  // Public or otherwise doesn't matter here.
  public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

  Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

  foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

  extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift needs to reach into module M and add `CProtocol.self` to the `Foo.self.conformsTo` list. This is perfectly doable for a concrete type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

  extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types conforming to `AProtocol`, recursively, and add `CProtocol.self` to their conformance list. The other is to scrap the flat list of conformances and instead make `is` and `as?` recursively search each protocol. Either way, you have replaced a fast, flat operation with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you must not only recursively search the list, but also evaluate the condition to see if it applies in this case. But the general problem of having to replace a fast search with a slow search applies either way.

···

On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

--
Brent Royal-Gordon
Architechies


(Haravikk) #3

Great explanation! This switch from flat to recursively searched though seems like it would only occur when the extension is in an external module though; for internal modules would it not still be possible to determine the flat list for each type? In that case extending a type from another module could be either disallowed, or produce a warning to indicate the performance implication?

The feature would still be very useful even just for internal use after all. Also it seems useful on a relatively small number of types, and the number of external modules that need/want to do this must narrow that even further, so external extensions may be quite niche, i.e- not worth losing the feature for internal use if that is indeed easier?

···

On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

My understanding—which may be incorrect, by the way—is that the issue is mainly with protocol extensions adding conformances, not specifically with those conformances being conditional, and that it specifically has to do with `is` and `as?` checks across module boundaries.

Suppose you have these declarations in module M:

  public protocol AProtocol {…}
  public protocol BProtocol: AProtocol {…}
  public protocol CProtocol {…}
  
  // Public or otherwise doesn't matter here.
  public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

  Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

  foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

  extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift needs to reach into module M and add `CProtocol.self` to the `Foo.self.conformsTo` list. This is perfectly doable for a concrete type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

  extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types conforming to `AProtocol`, recursively, and add `CProtocol.self` to their conformance list. The other is to scrap the flat list of conformances and instead make `is` and `as?` recursively search each protocol. Either way, you have replaced a fast, flat operation with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you must not only recursively search the list, but also evaluate the condition to see if it applies in this case. But the general problem of having to replace a fast search with a slow search applies either way.


(Patrick Pijnappel) #4

I'm not very familiar with the runtime so forgive me – the protocols would
only have to be added once right? And couldn't this usually be done at
compile time, or does it happen when the module is linked at startup?

···

On Thu, Aug 4, 2016 at 4:36 PM, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

> On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:
>
>> On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> I was wondering why this would put any more of a burden on the runtime
>> than simple inheritance of protocols. The way this could be
>> implemented is to augment the ConformanceTable for nominal types by
>> looking up its protocol extension’s inheritance clauses. I can
>> definitely see this impacting compile time but I don’t see why runtime
>> performance will be any different than simple inheritance. Further,
>> cyclic chains can be detected and broken (compiler error) during the
>> second pass of semantic analysis.
>
> My understanding—which may be incorrect, by the way—is that the issue is
mainly with protocol extensions adding conformances, not specifically with
those conformances being conditional, and that it specifically has to do
with `is` and `as?` checks across module boundaries.
>
> Suppose you have these declarations in module M:
>
> public protocol AProtocol {…}
> public protocol BProtocol: AProtocol {…}
> public protocol CProtocol {…}
>
> // Public or otherwise doesn't matter here.
> public struct Foo: BProtocol {…}
>
> Foo essentially has a flat list of the protocols it conforms to attached
to it. Notionally, you can think of that list as looking like:
>
> Foo.self.conformsTo = [BProtocol.self, AProtocol.self]
>
> And when you write `foo is CProtocol`, that eventually translates into:
>
> foo.dynamicType.conformsTo.contains(CProtocol.self)
>
> For a `Foo`, since the `conformsTo` list doesn't include
`CProtocol.self`, it returns `false`.
>
> Now imagine that you write a new module, N, and in it you say:
>
> extension Foo: CProtocol {…}
>
> You have now retroactively conformed `Foo` to `CProtocol`. Swift needs
to reach into module M and add `CProtocol.self` to the
`Foo.self.conformsTo` list. This is perfectly doable for a concrete
type—it's one flat list, after all.
>
> Instead, though, imagine that module N extended `AProtocol` to add a
conformance:
>
> extension AProtocol: CProtocol {…}
>
> There are two ways to handle this. One is to find all types conforming
to `AProtocol`, recursively, and add `CProtocol.self` to their conformance
list. The other is to scrap the flat list of conformances and instead make
`is` and `as?` recursively search each protocol. Either way, you have
replaced a fast, flat operation with a slow, recursive one.
>
> Conditional conformance adds another wrinkle to this, of course—you must
not only recursively search the list, but also evaluate the condition to
see if it applies in this case. But the general problem of having to
replace a fast search with a slow search applies either way.

Great explanation! This switch from flat to recursively searched though
seems like it would only occur when the extension is in an external module
though; for internal modules would it not still be possible to determine
the flat list for each type? In that case extending a type from another
module could be either disallowed, or produce a warning to indicate the
performance implication?

The feature would still be very useful even just for internal use after
all. Also it seems useful on a relatively small number of types, and the
number of external modules that need/want to do this must narrow that even
further, so external extensions may be quite niche, i.e- not worth losing
the feature for internal use if that is indeed easier?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #5

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

My understanding—which may be incorrect, by the way—is that the issue is mainly with protocol extensions adding conformances, not specifically with those conformances being conditional, and that it specifically has to do with `is` and `as?` checks across module boundaries.

Suppose you have these declarations in module M:

   public protocol AProtocol {…}
   public protocol BProtocol: AProtocol {…}
   public protocol CProtocol {…}
   
   // Public or otherwise doesn't matter here.
   public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

   Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

   foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

   extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift needs to reach into module M and add `CProtocol.self` to the `Foo.self.conformsTo` list. This is perfectly doable for a concrete type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

   extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types conforming to `AProtocol`, recursively, and add `CProtocol.self` to their conformance list. The other is to scrap the flat list of conformances and instead make `is` and `as?` recursively search each protocol. Either way, you have replaced a fast, flat operation with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you must not only recursively search the list, but also evaluate the condition to see if it applies in this case. But the general problem of having to replace a fast search with a slow search applies either way.

Great explanation! This switch from flat to recursively searched though seems like it would only occur when the extension is in an external module though; for internal modules would it not still be possible to determine the flat list for each type? In that case extending a type from another module could be either disallowed, or produce a warning to indicate the performance implication?

The feature would still be very useful even just for internal use after all. Also it seems useful on a relatively small number of types, and the number of external modules that need/want to do this must narrow that even further, so external extensions may be quite niche, i.e- not worth losing the feature for internal use if that is indeed easier?

Swift doesn't really have any features that stop working across modules. We're okay with the programmer having to think more and be more explicit across module boundaries (since it is API design at that point), but it'd take a very strong argument to have different runtime semantics across module boundaries.

FWIW, I'm planning to write a complete proposal for conditional conformances and will start posting drafts once it is far enough along to be useful. It won't have support for protocols conforming to other protocols, though.

  - Doug

···

Sent from my iPhone

On Aug 4, 2016, at 2:36 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:
On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

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


(Dave Abrahams) #6

So “a Collection is Equatable if its elements are?” == not supported?

···

on Thu Aug 11 2016, Douglas Gregor <swift-evolution@swift.org> wrote:

Sent from my iPhone

On Aug 4, 2016, at 2:36 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

My understanding—which may be incorrect, by the way—is that the
issue is mainly with protocol extensions adding conformances, not
specifically with those conformances being conditional, and that it
specifically has to do with `is` and `as?` checks across module
boundaries.

Suppose you have these declarations in module M:

   public protocol AProtocol {…}
   public protocol BProtocol: AProtocol {…}
   public protocol CProtocol {…}
   
   // Public or otherwise doesn't matter here.
   public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

   Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

   foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

   extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift
needs to reach into module M and add `CProtocol.self` to the
`Foo.self.conformsTo` list. This is perfectly doable for a concrete
type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

   extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types
conforming to `AProtocol`, recursively, and add `CProtocol.self` to
their conformance list. The other is to scrap the flat list of
conformances and instead make `is` and `as?` recursively search
each protocol. Either way, you have replaced a fast, flat operation
with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you
must not only recursively search the list, but also evaluate the
condition to see if it applies in this case. But the general
problem of having to replace a fast search with a slow search
applies either way.

Great explanation! This switch from flat to recursively searched
though seems like it would only occur when the extension is in an
external module though; for internal modules would it not still be
possible to determine the flat list for each type? In that case
extending a type from another module could be either disallowed, or
produce a warning to indicate the performance implication?

The feature would still be very useful even just for internal use
after all. Also it seems useful on a relatively small number of
types, and the number of external modules that need/want to do this
must narrow that even further, so external extensions may be quite
niche, i.e- not worth losing the feature for internal use if that is
indeed easier?

Swift doesn't really have any features that stop working across
modules. We're okay with the programmer having to think more and be
more explicit across module boundaries (since it is API design at that
point), but it'd take a very strong argument to have different runtime
semantics across module boundaries.

FWIW, I'm planning to write a complete proposal for conditional
conformances and will start posting drafts once it is far enough along
to be useful. It won't have support for protocols conforming to other
protocols, though.

--
-Dave


#7

Sent from my iPhone

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

My understanding—which may be incorrect, by the way—is that the issue is mainly with protocol extensions adding conformances, not specifically with those conformances being conditional, and that it specifically has to do with `is` and `as?` checks across module boundaries.

Suppose you have these declarations in module M:

   public protocol AProtocol {…}
   public protocol BProtocol: AProtocol {…}
   public protocol CProtocol {…}
   
   // Public or otherwise doesn't matter here.
   public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

   Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

   foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

   extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift needs to reach into module M and add `CProtocol.self` to the `Foo.self.conformsTo` list. This is perfectly doable for a concrete type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

   extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types conforming to `AProtocol`, recursively, and add `CProtocol.self` to their conformance list. The other is to scrap the flat list of conformances and instead make `is` and `as?` recursively search each protocol. Either way, you have replaced a fast, flat operation with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you must not only recursively search the list, but also evaluate the condition to see if it applies in this case. But the general problem of having to replace a fast search with a slow search applies either way.

Great explanation! This switch from flat to recursively searched though seems like it would only occur when the extension is in an external module though; for internal modules would it not still be possible to determine the flat list for each type? In that case extending a type from another module could be either disallowed, or produce a warning to indicate the performance implication?

The feature would still be very useful even just for internal use after all. Also it seems useful on a relatively small number of types, and the number of external modules that need/want to do this must narrow that even further, so external extensions may be quite niche, i.e- not worth losing the feature for internal use if that is indeed easier?

Swift doesn't really have any features that stop working across modules. We're okay with the programmer having to think more and be more explicit across module boundaries (since it is API design at that point), but it'd take a very strong argument to have different runtime semantics across module boundaries.

FWIW, I'm planning to write a complete proposal for conditional conformances and will start posting drafts once it is far enough along to be useful. It won't have support for protocols conforming to other protocols, though.

  - Doug

Hi Doug,

When can we expect your proposal?

Best regards
Maximilian

···

Am 12.08.2016 um 04:39 schrieb Douglas Gregor via swift-evolution <swift-evolution@swift.org>:

On Aug 4, 2016, at 2:36 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:
On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

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

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


(Paul Cantrell) #8

FWIW, I'm planning to write a complete proposal for conditional conformances and will start posting drafts once it is far enough along to be useful.

I’m looking forward to that!

I’ll throw a potential use case for this your way, both one of the most obvious and one of the nastiest I’ve encountered, in case it helps refine the proposal and/or clarify its limits:

    // No more runtime exceptions from things buried in
    // dictionaries! Wouldn’t it be nice?

    protocol JSONRepresentable { }

    // So far so good:

    extension String: JSONRepresentable { }

    // Hmm, extending protocols is out:

    extension Integer: JSONRepresentable { }
    extension FloatingPoint: JSONRepresentable { }

    // I can imagine a universe where this works:

    extension Optional: JSONRepresentable
        where Wrapped: JSONRepresentable { }

    // ...but this, oh dear:

    extension Optional.None: JSONRepresentable { }

    // Collection would be better but ... extending protocols again:

    extension Array: JSONRepresentable
        where Element: JSONRepresentable { }

    extension Dictionary: JSONRepresentable
        where Key == String, Value: JSONRepresentable { }

I imagine this is all a bridge too far for a proposal at this time — and maybe a bridge too far for any conditional protocol conformance ever — but it seems like it needs addressing.

It won't have support for protocols conforming to other protocols, though.

Curious. Why not? (I’ll happily wait for the proposal if you explain it there.)

Cheers,

Paul

···

On Aug 11, 2016, at 9:39 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:


(Douglas Gregor) #9

Sent from my iPhone

I was wondering why this would put any more of a burden on the runtime
than simple inheritance of protocols. The way this could be
implemented is to augment the ConformanceTable for nominal types by
looking up its protocol extension’s inheritance clauses. I can
definitely see this impacting compile time but I don’t see why runtime
performance will be any different than simple inheritance. Further,
cyclic chains can be detected and broken (compiler error) during the
second pass of semantic analysis.

My understanding—which may be incorrect, by the way—is that the issue is mainly with protocol extensions adding conformances, not specifically with those conformances being conditional, and that it specifically has to do with `is` and `as?` checks across module boundaries.

Suppose you have these declarations in module M:

   public protocol AProtocol {…}
   public protocol BProtocol: AProtocol {…}
   public protocol CProtocol {…}
   
   // Public or otherwise doesn't matter here.
   public struct Foo: BProtocol {…}

Foo essentially has a flat list of the protocols it conforms to attached to it. Notionally, you can think of that list as looking like:

   Foo.self.conformsTo = [BProtocol.self, AProtocol.self]

And when you write `foo is CProtocol`, that eventually translates into:

   foo.dynamicType.conformsTo.contains(CProtocol.self)

For a `Foo`, since the `conformsTo` list doesn't include `CProtocol.self`, it returns `false`.

Now imagine that you write a new module, N, and in it you say:

   extension Foo: CProtocol {…}

You have now retroactively conformed `Foo` to `CProtocol`. Swift needs to reach into module M and add `CProtocol.self` to the `Foo.self.conformsTo` list. This is perfectly doable for a concrete type—it's one flat list, after all.

Instead, though, imagine that module N extended `AProtocol` to add a conformance:

   extension AProtocol: CProtocol {…}

There are two ways to handle this. One is to find all types conforming to `AProtocol`, recursively, and add `CProtocol.self` to their conformance list. The other is to scrap the flat list of conformances and instead make `is` and `as?` recursively search each protocol. Either way, you have replaced a fast, flat operation with a slow, recursive one.

Conditional conformance adds another wrinkle to this, of course—you must not only recursively search the list, but also evaluate the condition to see if it applies in this case. But the general problem of having to replace a fast search with a slow search applies either way.

Great explanation! This switch from flat to recursively searched though seems like it would only occur when the extension is in an external module though; for internal modules would it not still be possible to determine the flat list for each type? In that case extending a type from another module could be either disallowed, or produce a warning to indicate the performance implication?

The feature would still be very useful even just for internal use after all. Also it seems useful on a relatively small number of types, and the number of external modules that need/want to do this must narrow that even further, so external extensions may be quite niche, i.e- not worth losing the feature for internal use if that is indeed easier?

Swift doesn't really have any features that stop working across modules. We're okay with the programmer having to think more and be more explicit across module boundaries (since it is API design at that point), but it'd take a very strong argument to have different runtime semantics across module boundaries.

FWIW, I'm planning to write a complete proposal for conditional conformances and will start posting drafts once it is far enough along to be useful. It won't have support for protocols conforming to other protocols, though.

  - Doug

Hi Doug,

When can we expect your proposal?

Within the next few weeks, I hope.

  - Doug

···

Sent from my iPhone

On Sep 10, 2016, at 2:26 PM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Am 12.08.2016 um 04:39 schrieb Douglas Gregor via swift-evolution <swift-evolution@swift.org>:

On Aug 4, 2016, at 2:36 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 4 Aug 2016, at 03:19, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:
On Aug 3, 2016, at 10:17 AM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

Best regards
Maximilian

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

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


(Douglas Gregor) #10

FWIW, I'm planning to write a complete proposal for conditional conformances and will start posting drafts once it is far enough along to be useful.

I’m looking forward to that!

I’ll throw a potential use case for this your way, both one of the most obvious and one of the nastiest I’ve encountered, in case it helps refine the proposal and/or clarify its limits:

    // No more runtime exceptions from things buried in
    // dictionaries! Wouldn’t it be nice?

    protocol JSONRepresentable { }

    // So far so good:

    extension String: JSONRepresentable { }

    // Hmm, extending protocols is out:

    extension Integer: JSONRepresentable { }
    extension FloatingPoint: JSONRepresentable { }

    // I can imagine a universe where this works:

    extension Optional: JSONRepresentable
        where Wrapped: JSONRepresentable { }

Fine so far.

    // ...but this, oh dear:

    extension Optional.None: JSONRepresentable { }

This will be ill-formed; Optional.None isn’t a type, it’s part of the value, and allowing the conformance to be present or absent based on a run-time value is a massive complication:

  func f<T: JSONRepresentable>(_: T) { }
  func g(stringOpt: String?) { f(stringOpt) } // whether the conformance applies or not depends on the run-time value!

    // Collection would be better but ... extending protocols again:

    extension Array: JSONRepresentable
        where Element: JSONRepresentable { }

    extension Dictionary: JSONRepresentable
        where Key == String, Value: JSONRepresentable { }

I imagine this is all a bridge too far for a proposal at this time — and maybe a bridge too far for any conditional protocol conformance ever — but it seems like it needs addressing.

These last two should be fine.

It won't have support for protocols conforming to other protocols, though.

Curious. Why not? (I’ll happily wait for the proposal if you explain it there.)

The generics manifesto has a sketch of the reason; I will elaborate in the proposal.

  - Doug

···

On Sep 12, 2016, at 8:14 AM, Paul Cantrell <cantrell@pobox.com> wrote:

On Aug 11, 2016, at 9:39 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Paul Cantrell) #11

    // ...but this, oh dear:

    extension Optional.None: JSONRepresentable { }

This will be ill-formed; Optional.None isn’t a type, it’s part of the value, and allowing the conformance to be present or absent based on a run-time value is a massive complication:

  func f<T: JSONRepresentable>(_: T) { }
  func g(stringOpt: String?) { f(stringOpt) } // whether the conformance applies or not depends on the run-time value!

Got it. This is more or less what I meant by “oh dear,” but better spelled out.

Two thoughts pop up from your response:

1. I just realized that my extension Optional.None is totally unnecessary. This covers nil as well as wrapped values:

    extension Optional: JSONRepresentable
        where Wrapped: JSONRepresentable

…and there’s no need to special-case nil.

I still fall into the trap of forgetting that in Swift, different nils can have different types. I still vaguely think of there being a single-value NilType (wrong) that’s a subtype (wrong) of all reference types (wrong). Old habits die hard!

2. That notwithstanding, I’ve come across a few cases where it would be useful to have individual enum cases either be different subtypes of their enclosing enum type, or conform to different protocols. I’ll post next time I hit a useful example of that situation.

It won't have support for protocols conforming to other protocols, though.

Curious. Why not? (I’ll happily wait for the proposal if you explain it there.)

The generics manifesto has a sketch of the reason; I will elaborate in the proposal.

I await it with patient excitement. :slight_smile:

Cheers, P

···

On Sep 12, 2016, at 12:04 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Sep 12, 2016, at 8:14 AM, Paul Cantrell <cantrell@pobox.com <mailto:cantrell@pobox.com>> wrote: