> 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