On Thu, Aug 25, 2016 at 11:28 AM, Matthew Johnson <matt...@anandabits.com> wrote:
> > On Aug 25, 2016, at 11:15 AM, Xiaodi Wu <xiaodi...@gmail.com> wrote: > > On Thu, Aug 25, 2016 at 10:07 AM, Matthew Johnson <matt...@anandabits.com> > wrote: > >> >> On Aug 25, 2016, at 9:37 AM, Xiaodi Wu <xiaodi...@gmail.com> wrote: >> >> Charles clarified that indeed he was pitching casting operators that >> match subclasses. >> >> >> Ok, I missed that. >> >> >> If the compiler knows that a class is sealed, why do you think there's a >> new keyword needed for the compiler to prove exhaustiveness? >> >> >> I said I wasn’t sure if there was a different / better way to do it. >> Just that this *would* do it. >> >> >> First, you can already match exact types by writing `type(of: instance) >> == Base.self` (which doesn't evaluate to true if instance is of a subclass >> of Base). >> >> >> This might be an alternative if the compiler adds special knowledge of >> this syntax to prove exhaustiveness. >> > > I might be in favor of that. As it is, I can write this: > > ``` > func ~= <T, U>(lhs: T.Type, rhs: U.Type) -> Bool { > return lhs == rhs > } > > class Base { > init() { } > } > class A1 : Base { } > class A2 : Base { } > > let a = A1() > > switch type(of: a) { > case A1.self: > print(1) > case A2.self: > print(2) > case Base.self: > print(0) > default: > fatalError() > } > ``` > > It'd be nice if the compiler would know about exhaustiveness (and if I > didn't have to define my own `~=`). This is, afaict, doable without any > additional syntax in the language. > > Second, if your class hierarchy is Base > A > B > C, then _even if_ there >> existed no way to match exact types (which there is), you have the option >> of switching over the type of an instance, providing cases that match in >> the order C, B, A, Base in order to perform a different action for each. >> This requires no additional knowledge at compile time beyond what you >> already stipulated for your use case, namely that the entire class >> hierarchy must be known at compile time. >> >> >> This order requirement is fragile. If you put Base first it will always >> match, which probably isn’t the intent. I would like to see a solution >> that requires you to match each type in the hierarchy without being subject >> to bugs related to ordering of the cases. >> > > Given that the hierarchy is known at compile-time, a solution that would > meet your criteria (not being subject to bugs related to ordering) would be > diagnostics for unreachable cases (i.e., if Base is matched before A, `case > is A` should be flagged as unreachable). > > > That isn’t an adequate solution IMO. First, it forces an ordering of the > cases. But more importantly, it will still allow accidentally matching > superclass and omitting subclass cases (so long as there are no unreachable > cases). > You cannot accidentally match superclasses by incorrect ordering, because it will always result in unreachable cases in a language without multiple inheritance. As to omitting subclasses, what's the harm in that? Suppose you had a hierarchy Base > A > B > C, but Joe the Programmer doesn't know about C. So he switches over the cases exhaustively and uses Base, A, and B exclusively. What problems will he encounter? > I would prefer a solution that requires *all classes* in the hierarchy to > be matched exactly. The existing casts would still work for cases when you > don’t care about that and *do* want to use a superclass to match subclasses. > I don't understand the use case. This seems esoteric and goes beyond exhaustive pattern matching. > >> Third, your motivating example in the previous thread already works. >> Slightly renamed to match the examples above, the following compiles: >> >> >> ``` >> class Base { init() { } } class A1 : Base { } class A2 : Base { } func >> foo(_ b: Base) -> Int { switch b { case is A1: return 1 case is A2: return >> 2 case is Base: return 0 } } >> >> let a = A1() let b = A2() foo(a) // 1 foo(b) // 2 >> ``` >> >> There is a warning that `case is Base` is always true. Perhaps something >> could be done about that diagnostic, since that is after all what you want >> in a switch statement without a default case. >> >> I'm sure you were aware of all of these points, so I guess I'm asking, >> what exactly are you pitching? >> >> >> See above. I am looking for a solution that avoids this warning >> precisely because it will *not* always be true. The compiler gaining >> special knowledge of the `type(of: instance) == Base.self` pattern could >> be *part* of a solution but it still doesn’t bind a name the correct type. >> For example, with the Base > A > B > C hierarchy when I match `type(of: >> instance) == B.self` I also want a variable bound with a type of `B`. This >> gets pretty verbose and requires the compiler to have special knowledge of >> pretty specific pattern: >> >> func foo(_ b: Base) -> Int { >> switch b { >> case let base as Base where type(of: instance) == Base.self: return 1 >> case let a as A where type(of: instance) == A.self: return 2 >> case let b as B where type(of: instance) == B.self: return 3 >> case let c as C where type(of: instance) == C.self: return 4 >> } >> } >> > > >> If the compiler could prove exhaustiveness here I would accept that >> solution. But it seems like an exact match cast operator would be much >> more elegant. >> >> In any case, anything that requires matching every type in a hierarchy >> without being subject to case ordering bugs and doesn’t require a default >> clause would be acceptable to me. That is the problem I would like to see >> solved. >> > > Looking back, it seems like diagnostics for unreachable cases would meet > your criteria exactly and would be the most straightforward. I don't think > it would even require an evolution proposal. I would love to see type(of:) > work with switch statements out-of-the-box, but that seems more esoteric. > None of this requires additional syntax, IMHO. > > > Diagnostics would be an improvement for sure, but aren’t a complete > solution IMO for the reasons noted above. We don’t necessarily need new > syntax but I don’t have any great alternatives at the moment. > > > >> >> On Thu, Aug 25, 2016 at 08:40 Matthew Johnson <matt...@anandabits.com> >> wrote: >> >>> On Aug 24, 2016, at 9:33 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote: >>> >>> On Wed, Aug 24, 2016 at 9:25 PM, Matthew Johnson via swift-evolution < >>> swift-evolution@swift.org> wrote: >>> >>>> >>>> On Aug 24, 2016, at 9:09 PM, Robert Widmann via swift-evolution < >>>> swift-evolution@swift.org> wrote: >>>> >>>> I have 3 qualms with this proposal as it stands: >>>> >>>> - type(of:) will never lie to you. >>>> >>>> The only question it won’t answer to your satisfaction is the dynamic >>>> type of the NSString you boxed up as an Any. >>>> >>>> - No more keywords without significant justification. >>>> >>>> I don’t buy the performance use case at all - if you were properly >>>> concerned about performance you would try to use as many of Swift’s static >>>> features as possible. >>>> >>>> - Especially no more keywords that look like they belong in Rust or PHP! >>>> >>>> There is no precedent for the spelling of these operations other than >>>> the suffixed punctuation. Given that they’re domain-specific, will >>>> definitely be hard to use (given that NSString(string: "Bar”) may not >>>> “really” given you an NSString yet that’s what you asked us to check for “ >>>> *really*"), and will be obviated by the implementation of SE-0083, I >>>> can’t see a reason why we need any of this in the language proper. >>>> >>>> >>>> One related topic to consider is exhaustive pattern matching for >>>> classes. Now that SE-0117 has been accepted it will be possible to do this >>>> for many classes (I would say most if it weren’t for Objective-C classes >>>> being so common in Swift and are imported as `open`). Supporting >>>> exhaustive pattern matching well would require some kind of syntax for >>>> matching the runtime type exactly. I have imagined this as being “exact >>>> match” cast operators, which is what the `really_*` operators are. >>>> >>> >>> I don't understand. As pitched, these operators remove bridging magic, >>> but `Subclass really_is Superclass == true`. How would you use this for >>> classes? >>> >>> >>> Bridging is the use case motivating the pitch. I am bringing up a >>> related use case. >>> >>> The pitch does not specify `Subclass really_is Superclass == true` and I >>> would argue that this is not the semantics we would want. My >>> interpretation of the proposed solution is: >>> >>> "I propose the following operators: really_is, really_as, really_as?, >>> and really_as!. These operators would only return a positive result if the >>> type actually was what was being asked for, instead of something that might >>> be able to bridge to that type *or a superclass of that type*." >>> >>> We discussed the exhaustive pattern matching previously in this thread: >>> https://lists.swift.org/pipermail/swift-evolution/We >>> ek-of-Mon-20160523/018799.html where the “exact match” cast operators >>> were called `isExactly` and `asExactly`. >>> >>> I think the exhaustive pattern matching use case for classes (and >>> protocols if / when we get sealed protocols) is an important one. I also >>> think doing it right requires the ability to match exact types (i.e. not >>> match subclasses). Maybe there is a better mechanism than a new operators >>> but they would certainly do the job well. >>> >>> >>> >>>> Do you have an alternative in mind for exhaustive pattern matching if >>>> we do not introduce exact match cast operators? >>>> >>>> >>>> ~Robert Widmann >>>> >>>> On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution < >>>> swift-evolution@swift.org> wrote: >>>> >>>> MOTIVATION: >>>> >>>> SE-0083 appears to be dead in the water, having been deferred until >>>> later in Swift 3 back in May and not having been heard from since then, >>>> with the Swift 3 release looming closer and closer. However, the >>>> predictability gains that would have been provided by this change remain >>>> desirable for cases where one needs to know the actual dynamic type of an >>>> entity before any bridging magic is involved. Additionally, >>>> performance-critical code may desire the ability to check something’s type >>>> quickly without incurring the overhead of Objective-C bridging code. >>>> >>>> PROPOSED SOLUTION: >>>> >>>> I propose the following operators: really_is, really_as, really_as?, >>>> and really_as!. These operators would only return a positive result if the >>>> type actually was what was being asked for, instead of something that might >>>> be able to bridge to that type. >>>> >>>> DETAILED DESIGN: >>>> >>>> let foo: Any = "Foo" >>>> let bar: Any = NSString(string: "Bar") >>>> >>>> let fooIsString = foo is String // true >>>> let fooReallyIsString = foo really_is String // true >>>> >>>> let fooIsNSString = foo is NSString // true >>>> let fooReallyIsNSString = foo really_is NSString // false >>>> >>>> let barIsString = bar is String // true >>>> let barReallyIsString = bar really_is String // false >>>> >>>> let barIsNSString = bar is NSString // true >>>> let barReallyIsNSString = bar really_is NSString // true >>>> >>>> ALTERNATIVES CONSIDERED: >>>> >>>> Stick with using an unholy combination of Mirror and unsafeBitCast when >>>> you need to know what you’ve actually got. >>>> >>>> Charles >>>> >>>> _______________________________________________ >>>> 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 >>>> >>>> >>>> >>>> _______________________________________________ >>>> 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