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?
Maybe in that case he won’t encounter any problems. However, if you have:
Base
> >
C1 C2
> >
D1 D2
And you just switch over Base, C1, and C2 you may have a match for all
possible runtime values but you don’t have cases for all of the possible
type. Imagine if you have code that is initially written before D1 and D2
are introduced and your intent is to include a case for each possible
runtime type. You want the code to fail to compile when D1 and D2 are
introduced until you revisit the switch statement
I understand that this is what you want, but what is your use case for
this? Why should you want your code to fail to compile?
To be sure, it is currently *possible* to get this behavior (at runtime,
admittedly) by having a default case that calls fatalError() after a series
of cases that explicitly compare dynamic type using type(of:) and ==, but
this is a code smell at the very least, and certainly not something we
should want to make easier with a keyword (even if that allows the failure
to manifest at compile time). For one, it breaks our understanding of
library versioning:
Library Foo defines the following class hierarchy, which is entirely public:
Base (sealed) > A > B > C
Sally uses library Foo and uses your proposed keyword in an exhaustive
switch.
The next minor release of library Foo (and it's a minor release because the
public API is unchanged) adds an internal subclass of C called __C (this
would be analogous to how NSString and __NSCFString are related, but of
course those are Objective-C classes in Darwin Foundation).
Various methods that formerly returned a value of type C now return a value
of type __C upcast as C. Sally's code ceases to compile, and through no
fault of the library developer. There is nothing she can do using your
proposed keyword to fix this defect, because __C is not public. She *must*
use casting operators that match subclasses.
In the next minor version of library Foo, __C is no longer necessary and is
removed. Sally's code doesn't break this time, because she stopped using
your proposed keyword.
, just as it does when you add a new case to an enum. However, given the
current tools there is nothing you can do to force this. This is what I
meant by “accidentally”.
Having a tool for preventing these kinds of mistakes seems like a useful
thing and puts classes (and protocols if it works with them as well when
all conforming types are known) on equal footing with enums in terms of
pattern matching. This gives us more options for structuring our code.
I'd have to be convinced that this tool is at all wise with a compelling
use case. IMO, it encourages far, far more fragile code than a poorly
ordered series of cases would do currently. Therefore, at the moment, it
seems to me that this tool would be the greater mistake.
To be clear, your pitch has nothing to do with exhaustiveness on par with
enums, because it is possible to write switch statements over class
hierarchies either exhaustively or non-exhaustively using the existing
casting operators; the compiler can be given enough smarts to emit the
appropriate errors when the cases are non-exhaustive and to stop insisting
on a `default` case when the cases are exhaustive. I'd be absolutely in
favor of that change.
···
On Thu, Aug 25, 2016 at 3:24 PM, Matthew Johnson <matthew@anandabits.com> wrote:
On Aug 25, 2016, at 11:56 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Thu, Aug 25, 2016 at 11:28 AM, Matthew Johnson <matthew@anandabits.com> > wrote:
On Aug 25, 2016, at 11:15 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Thu, Aug 25, 2016 at 10:07 AM, Matthew Johnson <matthew@anandabits.com >> > wrote:
On Aug 25, 2016, at 9:37 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
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@anandabits.com> >>> wrote:
On Aug 24, 2016, at 9:33 PM, Xiaodi Wu <xiaodi.wu@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