[swift-evolution] Pitch: really_is and really_as operators

Xiaodi Wu xiaodi.wu at gmail.com
Thu Aug 25 11:56:39 CDT 2016


On Thu, Aug 25, 2016 at 11:28 AM, Matthew Johnson <matthew at anandabits.com>
wrote:

>
> On Aug 25, 2016, at 11:15 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> On Thu, Aug 25, 2016 at 10:07 AM, Matthew Johnson <matthew at anandabits.com>
>  wrote:
>
>>
>> On Aug 25, 2016, at 9:37 AM, Xiaodi Wu <xiaodi.wu at 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 <matthew at anandabits.com>
>> wrote:
>>
>>> On Aug 24, 2016, at 9:33 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>> On Wed, Aug 24, 2016 at 9:25 PM, Matthew Johnson via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>>>
>>>> On Aug 24, 2016, at 9:09 PM, Robert Widmann via swift-evolution <
>>>> swift-evolution at 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 at 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 at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160825/da526a99/attachment.html>


More information about the swift-evolution mailing list