> On May 5, 2017, at 2:16 PM, Tony Allevato <tony.allev...@gmail.com> wrote:
> 
> On Fri, May 5, 2017 at 11:51 AM Matthew Johnson <matt...@anandabits.com 
> <mailto:matt...@anandabits.com>> wrote:
>> On May 5, 2017, at 1:33 PM, Tony Allevato <tony.allev...@gmail.com 
>> <mailto:tony.allev...@gmail.com>> wrote:
>> 
>> 
>> 
>> On Fri, May 5, 2017 at 11:07 AM Matthew Johnson <matt...@anandabits.com 
>> <mailto:matt...@anandabits.com>> wrote:
>>> On May 5, 2017, at 10:45 AM, Tony Allevato via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> 
>>> Thanks for your feedback, everybody!
>> 
>> Thanks for continuing to drive this forward!
>> 
>>> 
>>> I've updated the gist 
>>> <https://gist.github.com/allevato/2fd10290bfa84accfbe977d8ac07daad> to 
>>> reflect what seems to be a consensus here:
>>> 
>>> * Derived conformances are now opt-in (this makes the recursive case *much* 
>>> cleaner, and the complexity involved in that section has been completely 
>>> removed)
>> 
>> Can the opt-in conformance be declared in an extension?  If so, can the 
>> extension be in a different module than the original declaration?  If so, do 
>> you intend any restrictions, such as requiring all members of the type 
>> declared in a different module to be public?  My initial thought is that 
>> this should be possible as long as all members are visible.
>> 
>> Declaring the conformance in an extension in the same module should 
>> definitely be allowed; I believe this would currently be the only way to 
>> support conditional conformances (such as the `Optional: Hashable where 
>> Wrapped: Hashable` example in the updated draft), without requiring deeper 
>> syntactic changes.
>> 
>> I'm less sure about conformances being added in other modules, but I'm 
>> inclined to agree with your assessment. I could see two ways of interpreting 
>> it:
>> 
>> * E/H can only be derived in an extension in an external module if all the 
>> members are accessible (and the other conditions are met).
>> * E/H can be derived in an extension in an external module using only the 
>> subset of accessible members (if the other conditions are met).
>> 
>> These are subtly different. The argument for the first would be "if you want 
>> to add E/H to a type in a different module, you must *consciously* decide 
>> which members you want to use in those computations". The argument for the 
>> second would be "you can already make a type in a different module conform 
>> to E/H and you'd be restricted to the accessible members there, so let's 
>> make that path easier for users too."
>> 
>> The first case is probably the safer choice. I'm not sure about the 
>> implementation difficulty of each.
>> 
>> 
>>> * Classes are supported now as well
>>> 
>>> Please take a look at the updated version and let me know if there are any 
>>> concerns! If folks like it, I'll prepare a pull request.
>> 
>> Will the synthesis for classes dispatch through a non-final method which is 
>> expected to be overridden by subclasses?  You don’t explicitly state this 
>> but it seems implied.  If so, what if  the subclass requires a custom 
>> implementation?  This would require the signature of the non-final method to 
>> be part of the synthesis contract.
>> 
>> Supporting non-final classes introduces enough complexity (especially when 
>> multiple modules are involved).  I would hate to see it get sidetracked in 
>> discussions regarding non-final classes and miss the Swift 4 window because 
>> of that.  Given the limited time left for Swift 4 it might be better to keep 
>> the initial proposal simpler and consider a followup in the Swift 5 
>> timeframe to build on the initial proposal.
>> 
>> For ==, the operator must already be "class final" or "static" regardless of 
>> this proposal, and it can't be "overridden" as such in subclasses because 
>> the arguments would be different (lhs and rhs would be the subclass, not the 
>> superclass). So the compiler should be able to generate the correct 
>> implementation for subclasses in all cases, right?
> 
> This won’t work because Equatable has a `Self` requirement so the `==` 
> defined by the initial conforming class would be called.  In order to support 
> non-final classes you would need to have that dispatch through something like 
> an `isEqual` method which *can* be overridden.
> 
> Ah crap, you're right. I was basing my experimentation on this example:
> 
> class Base: Equatable {
>   let x: Int
>   init(x: Int) { self.x = x }
>   static func == (lhs: Base, rhs: Base) -> Bool { return lhs.x == rhs.x }
> }
> 
> class Sub: Base {
>   let y: Int
>   init(x: Int, y: Int) { self.y = y; super.init(x: x) }
> 
>   static func == (lhs: Sub, rhs: Sub) -> Bool {
>     guard lhs as Base == rhs as Base else {
>       return false
>     }
>     return lhs.y == rhs.y
>   }
> }
> 
> let s1 = Sub(x: 1, y: 2)
> let s2 = Sub(x: 1, y: 3)
> let s3 = Sub(x: 1, y: 2)
> 
> func usesBase(_ lhs: Base, _ rhs: Base) -> Bool { return lhs == rhs }
> func usesSub(_ lhs: Sub, _ rhs: Sub) -> Bool { return lhs == rhs }
> 
> print(usesSub(s1, s2)) // false (expected)
> print(usesSub(s1, s3)) // true
> 
> print(usesBase(s1, s2)) // true (problematic)
> print(usesBase(s1, s3)) // true
> 
> In that case the problematic behavior is "expected", but when you make it 
> generic, you're right that you still get the problematic behavior which kind 
> of makes the conformance useless:
> 
> func usesGeneric<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool {
>   return lhs == rhs
> }
> 
> print(usesGeneric(s1, s2)) // true (problematic)
> print(usesGeneric(s1, s3)) // true
> 
> Sadly, I don't work with classes/inheritance enough in Swift to run into 
> these problems on a regular basis.
> 
> FWIW, the same issue came up in SE-0091 
> <https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md>
>  and the consensus seemed to be that since Equatable already didn't work the 
> "right way" for inheritance anyway, that would need to be fixed separately, 
> so I'd be interested to know if the core team feels the same way here.
>  
> 
>> 
>> For hashValue, I think the possibilities are:
>> 
>> * Sub is a subclass of Super. Super conforms to Hashable and implements 
>> non-final hashValue. The compiler can derive it for Sub and call 
>> super.hashValue in its implementation.
> 
> Yes, this makes sense.  The primary difficulty with Hashable is that it 
> refines Equatable.  Refining a non-final implementation of `hashValue` is 
> relatively straightforward.
> 
> Right... there could be situations where a function that takes a generic <T: 
> Hashable> gets inconsistent results for == and hashValue because the latter 
> is dynamically dispatched but the former isn't. That's already the situation 
> we have today, but it could be argued that deriving implementations would 
> "make the bleeding worse".
>  
> 
>> * Sub is a subclass of Super. Super conforms to Hashable and implements a 
>> final hashValue. The compiler cannot derive one for Super and would silently 
>> not do so.
> 
> Do you mean “the compiler cannot derive one for Sub”?
> 
> Yes, sorry.
>  
> 
>> * Sub is a subclass of Super. Super does not conform to Hashable, but Sub 
>> asks to derive it. This can either (1) not be allowed, telling the user that 
>> they need to write it manually in this case, or (2) be allowed and use all 
>> accessible members to compute the hashValue (including those from the 
>> superclass).
>> 
>> What do Encodable/Decodable do in these situations? It seems similar 
>> solutions there would apply here.
> 
> That’s a good question.  I don’t recall whether this was addressed explicitly 
> or not.
> 
>> 
>> But after writing this all out, I'm inclined to agree that I'd rather see 
>> structs/enums make it into Swift 4 even if it meant pushing classes to Swift 
>> 4+x.
> 
> That is reasonable.  
> 
> On the other hand, I think you could come up with straightforward semantics 
> for synthesizing conformance for final classes as well.  Final classes with 
> no superclass should be straightforward.  Final classes that do have a 
> superclass would be similarly straightforward if we decide to allow this as 
> described in option (2) above regarding hashValue.  
> 
> I’m on the fence on this - if we can include final classes using option (2) 
> without jeopardizing getting this in for Swift 4 I would support that.  If 
> it’s going to put support for value types in Swift 4 at risk then I would not.
> 
> I'm on the fence as well. It sounds like we could scratch non-final classes 
> off the list for now without losing much because they're already very 
> problematic. The question then is, how much worse does supporting final 
> classes make the implementation.
> 
> Since I only did the work for enums in my initial experiment, I don't have 
> enough insight yet to know for sure. Hopefully I'll have some time in the 
> next couple days to try it out.

Sounds good.  Since you’re working on the implementation I’m happy deferring to 
your judgment.  :)

> 
> 
>> 
>>  
>> 
>>> 
>>> 
>>> On Fri, May 5, 2017 at 8:16 AM Nevin Brackett-Rozinsky via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> On Fri, May 5, 2017 at 1:47 AM, Xiaodi Wu via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> On Fri, May 5, 2017 at 12:41 AM, Brent Royal-Gordon <br...@architechies.com 
>>> <mailto:br...@architechies.com>> wrote:
>>> I would think only final classes could participate in this, since a 
>>> subclassable class would need to allow subclasses to override equality, and 
>>> you can't override a static `==` operator method.
>>> 
>>> I work so rarely with classes that I'm embarrassed to have to ask this 
>>> question: can classes not satisfy Equatable with a `public class func ==`?
>>> 
>>> Currently:
>>> 
>>> class C: Equatable {
>>>     class func == (lhs: C, rhs: C) -> Bool {
>>>         return lhs === rhs
>>>     }
>>> }
>>> 
>>> Yields an error, “Operator '==' declared in non-final class 'C' must be 
>>> 'final'”.
>>> 
>>> Nevin
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to