SE-0192 — Non-Exhaustive Enums

Why isn't that a problem today? Like I showed in that example, that's
undefined behavior and will potentially result in a bug (or even a crash if
instead of an int you end up with an unexpected nil pointer)

···

On Sat, Jan 6, 2018 at 4:47 PM Jon Shier <jon@jonshier.com> wrote:

Which isn’t a problem right now, AFAICT. Apps compiled under older SDKs
continue to work fine (sometimes better than when compiled under the new
SDK, as the older one avoids new bugs). My question is about how that
compatibility is accomplished today and how and why the Obj-C and Swift
cases are apparently different here.

Jon

On Jan 6, 2018, at 6:12 PM, Javier Soto <javier.api@gmail.com> wrote:

What doesn't happen today? The issue is not when they ship a new SDK: When
rebuilding your app against it, you'll get a warning for a missing case.
The problem is when running the app against a newer iOS version with a
newer version of the SDK where the enum has a new case.
On Sat, Jan 6, 2018 at 3:10 PM Jon Shier <jon@jonshier.com> wrote:

Except it clearly doesn’t happen today when Apple ships new SDKs.
Obviously there’s an alternate mechanism used in that case. I’m just
curious what it is and why Swift so desperately needs an alternative.

Jon

On Jan 6, 2018, at 5:49 PM, Javier Soto <javier.api@gmail.com> wrote:

This is very much an issue in Obj-C today. If you have an NS_ENUM defined
with cases A, B, and C, this switch is correct:

int foo;
swith (e) {
case A: foo = 0; break;
case B: foo = 1; break;
case C: foo = 2; break;
}

(Note the lack of a default case)

If that enum is defined in a framework and it changes after the app is
compiled (like it's the case with Apple frameworks), then that code
produces no warning, yet the foo variable will have a garbage value
(undefined behavior, but as far as the compiler can tell at compile time
your code is fine)

Adding a default clause to that switch has the downside of not getting
warnings for new added cases, like has been discussed before, which is very
useful.
On Fri, Jan 5, 2018 at 7:11 PM Jon Shier via swift-evolution < >> swift-evolution@swift.org> wrote:

At this point I think it might be useful to outline how binary
compatibility works for Objective-C on Apple platforms right now. As an app
developer I’m not intimately familiar with what happens when you run an app
compiled with the iOS 10 SDK on iOS 11. Are there just runtime checks to
call old code paths or something else? The more this thread goes on the
more confused I get about why Swift would have this issue while it doesn’t
appear to be one for Obj-C. If an enum adds a case now, I don’t have to
care until I recompile using the new SDK. Is the intention for Swift to be
different in this regard?

Jon Shier

On Jan 5, 2018, at 6:41 PM, Jordan Rose via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Jan 3, 2018, at 00:54, Jason Merchant via swift-evolution < >>> swift-evolution@swift.org> wrote:

Is it hard to imagine that most everyone can get what they want and keep
the syntax clean and streamlined at the same time? Without any "@" signs or
other compiler hints?

For what it's worth, the original version of the proposal started with a
modifier (a context-sensitive keyword, like 'final'), but the core team
felt that there were a lot of modifiers in the language already, and this
didn't meet the bar.

"Rather, we are how to enable the vendor of a nonexhaustive enum to add

new cases without breaking binaries compiled against previous versions"

When an enum changes, and the change causes the code to break, the user
can be presented with migration options from an automated IDE tool. In what
specific way does this not solve the issue about having to upgrade your
code when using someone else's code library? This very notion implies your
disgruntled about doing work when things are upgraded, is that really what
this fuss is all about?

A well written language interpreter and auto-tooling IDE would not need
hints embedded in the code syntax itself. Migration hints from version to
version should not be a part of either the past or future version of the
code library.

Thanks for bringing this up! Unfortunately, it falls down in practice,
because if there's a new enum case, *it's unclear what you want to do
with it.* If you're handling errors, it's not obvious that the way
you've handled any of the *other* errors is appropriate. In the
(admittedly controversial) SKPaymentTransactionState case, none of the
existing code would be appropriate to handle the newly-introduced
"deferred" case, and nor could StoreKit provide "template" code that would
be appropriate to the client app.

In any case, though, the key point on this particular quoted sentence is
"without breaking *binaries"*. Any such change must be valid *without* recompilation,
and indeed without any intervention from the developer or an IDE, because
that's what happens when the user updates their OS.

Jordan

...

I don't expect the community to agree on language grammar, but the
common sense here on how to achieve the intended goals seems to be out of
wack.

If someone can present a clear logical statement as to how an automated
migration tool behind the scenes in the IDE to handle all your versioning
worries, does not make this whole discussion about adding more convoluted
syntax additions irrelevant, I'd love to hear it.

___________________

Sincerely,
Jason

On Tue, Jan 2, 2018 at 12:36 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 2, 2018 at 12:11 PM, Jason Merchant via swift-evolution < >>>> swift-evolution@swift.org> wrote:

I think this whole thing has been unnecessarily convoluted. As a
result, the majority of the replies are rabbit holes.

In my opinion, the true root of the concept in question is as follows:

*A list of something is desired:*
1 - Pancake
2 - Waffle
3 - Juice

*Developer wishes to be able to:*
*A)* Add new things to the list of choices in the future as they come
up with new ideas
*B)* Sometimes select one of the choices to be chosen as the normal
choice if no choice is made by the user

A and B are *separate desires*. In some circumstances a developer may
want to add a new choice and make it the normal choice when there was no
normal choice was clarified before.

I don't think this is an accurate summary of the problem being tackled
here. Rather, we are how to enable the vendor of a nonexhaustive enum to
add new cases without breaking binaries compiled against previous versions.
There is little here to do with what a "default" should be. Indeed, it is
an explicit design decision of Swift not to support types having an
implicit default value.

____________________

*Part 2:*

After this simple desire is clear, there should be two discussions:
*A)* In a text only coding language, what would we like the syntax to
look like? (Without regard to past-bias. What should it really be, forget
what mistaken design choices were made in Swift in the past)
*B)* How do we approach making this happen behind the scenes?

*Bonus:* Given that some of us have changed our approach to
programming significantly beyond text based coding, and into more dynamic
mediums of programming in other niches, and even here and there in Xcode -
I would recommend considering how the IDE would show a modern version of
this concept. I feel too often that Swift design syntax has a *lack
of awareness between the distinctions of what the IDE should do, as opposed
to what the syntax of the language should be*, and what should be
handled behind the scenes by automated tooling.

_____________________

*My opinion*, in answering the above questions is in preference to a
simple easy to read and write syntax, something like the following:

choices Breakfast {
    Pancake, *Waffle*, Juice
}

If a "default" choice is desired, it is obvious to me that I would
select the choice from the IDE, and it would be visually indicated that it
was the default.

When changes occur, whether new choices are added, old ones are
removed or changed, or a default is added, changed, or removed - a behind
the scenes automated tool analyzes the changes and presents migration
options through the IDE.

_____________________

Sincerely,
Jason

_______________________________________________
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

--
Javier Soto

--

Javier Soto

--

Javier Soto

Having reviewed much of the commentary on this proposal, I keep coming back to the same thought: why not use @versioned and @available keywords for this instead of some concept related to “exhaustive”?

The issue here is not whether a given enum is “exhaustive” over the enumerated problem space; it’s whether the developer wants to alter the enum in the future without breaking ABI compatibility.

If you call it “exhaustive” then it’s misleading, because all enums at a given moment in time can be switched over exhaustively. This will just confuse folks.

Since versioning is really the main goal, why not use the same annotations for versioning enums as are used for versioning everything else?

@versioned
enum MyError: Error {
@available(OSX, deprecated:10.11, message: "this error case is going away in 10.12")
case BadThingHappened

@available(forever)
case ReallyBadThingHappened
}

etc.

That way you could have some cases get removed in the future as well as added, and you won’t confuse people by talking about “complete” or “exhaustive”, which are terms that are too closely coupled with the meaning and application of a given enum to which they refer.

It would be best to use terms that just say what they mean. We are talking about versioning APIs to keep ABI stability? Use @versioned and @available like everywhere else.

Or is there a compelling reason this cannot be done? I read much of the arguments here but didn’t see any mention of @versioned... maybe it’s iOS Mail app thinking I was looking for an email address that contains @versioned? ;D

Jonathan

1 Like

I had the same thought as Cheyo. It isn’t a deal breaker… I like the compromise, but I would prefer it trigger only on an actual unknown case (as opposed to acting like default). I like to break failure cases out at the top when possible. I don’t see any good reason not to support that style.

To answer your question, in the naive sense, it basically is the same question as asking if it is a known case (and then taking the inverse). That doesn’t mean actually checking each case separately though. For example, if the enum cases are internally represented as an unsigned integer, and they are all together in a block, the compiler could simply check that it is greater than the max known value. You could probably even do a bit mask comparison in some cases...

If it is inefficient for some reason, the compiler should be free to rearrange the order of things, as long as it doesn’t change the outcome.

Thanks,
Jon

···

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

We seem to agree that, by virtue of not supporting use in a pattern and being placed at the end, the feature is a flavor of default. I’m still not sure I understand why you believe it should not be a flavor of default going forward.

You still haven’t answered my question, though—what’s the use case for the feature you propose?

My use case would be distinguishing between compile time known cases vs “future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not deal breaker, just a different style I’d like to see supported in the future.

I'm still not sure I understand. How can the machine know that it's dealing with an unknown case without first checking if it matches any known case?

We seem to agree that, by virtue of not supporting use in a pattern and
being placed at the end, the feature is a flavor of default. I’m still not
sure I understand why you believe it should not be a flavor of default
going forward.

You still haven’t answered my question, though—what’s the use case for
the feature you propose?

My use case would be distinguishing between compile time known cases vs
“future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a
distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not
deal breaker, just a different style I’d like to see supported in the
future.

I'm still not sure I understand. How can the machine know that it's
dealing with an unknown case without first checking if it matches any known
case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the
compromise, but I would prefer it trigger only on an actual unknown case
(as opposed to acting like default). I like to break failure cases out at
the top when possible. I don’t see any good reason not to support that
style.

To answer your question, in the naive sense, it basically is the same
question as asking if it is a known case (and then taking the inverse).
That doesn’t mean actually checking each case separately though. For
example, if the enum cases are internally represented as an unsigned
integer, and they are all together in a block, the compiler could simply
check that it is greater than the max known value. You could probably even
do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax? What
do you gain from writing the unknown case first? Isn't this basically the
same thing as asking for the ability to write the default case first, a
frequently suggested and rejected syntax addition?

If it is inefficient for some reason, the compiler should be free to

···

On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com> wrote:

On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com> >> wrote:

rearrange the order of things, as long as it doesn’t change the outcome.

There are a number of things being talked about in this thread, all of
which are solved by proper automated IDE tooling.

Javier, In my opinion the situation you've described is based on how
overall nil is handled by the language parser before it generates LLVM IR.

A smart compiler would know what to do here without any changes to the
language syntax. (Having implemented a similar language parser for LLVM IR,
I can say for certain there is no excuse for the ugly syntax choices that
Swift has been making, and it appears to me the result of laziness and bad
design choices)

I solved this without setting a value before the switch/case statements and
without forcing a default clause to be written. No doubt, it has been clear
that these things appear troubling because of the incorrect design choices
and approach to the underlying problem.

I will present another situation outside of the realm of enums in which
Swift syntax has been made incorrect syntax choices in the handling of nil.

The most obvious is optionals. There are several things wrong with the
swift implementation of optionals:

user. This results in extra code the developer has to type, even if they
program in genres in which optional situations are irrelevant. (Forcing the
user to unwrap optionals with things like "if let" and "guard" is ugly,
bloated, and unnecessary) In my implementation I have seen that there is no
need for optionals to be written like this in the syntax.

B: The symbols "?" and "!" are littered throughout code needlessly as *compiler
hints*. Meaning the swift parser programmer team was essentially too lazy
to implement what I'm describing and sacrificed the syntax of the language
to avoid writing a parser that would be able to know if something was set
or nil without adding extra garbage to the syntax. Again in my
implementation, I have verified that this can be done and in fact if done
correctly, the resulting* compile time is faster* than the swift compiler,
and the code is cleaner.

What is happening in this thread is that there are core underlying
philosophies that the swift design has adopted which are incorrect
approaches to big picture problems. As a result of swift making these big
picture mistakes, there arise a plethora of these other troubles that come
as domino effects in circumstances here and there. This is why there is a
lot of talk about patching the syntax here or there, and forcing things to
be written, and then down wind of this trail of derailed logic, there arise
a number of people debating what grammar to use for something that
shouldn't be written in the first place. If the big picture problem is
solved, the majority of these discussions are mute.

If Swift can accept that these choices were incorrect, we can cleanup the
syntax and rewrite the parser within an impressively short time as I've
seen myself. However, this requires the agreement of those involved, and
based on this thread and the fact that this line of derailed logic has gone
on for several years, means that the likely response to what I'm suggesting
is one of "That sounds like a lot of work to change, and I already spent a
lot of time and effort making it like this" - Laziness is no excuse for
sticking to bad choices made in the past and causing more bad syntax
choices to be made in the present. If we are truly going to make swift the
dominant language for the future, then we have to adopt a different
attitude toward rewriting design flaws as a community.

We can't always see into the future and know if a choice will be good or
bad, but when we are far enough along the trail to see that the choices
swift made in the past and are a core part of the language, are in fact the
root of the problem, rather than clinging to backward compatibility of a
clearly misguided architecture, *we should do the wise thing and rewrite
the foundation of the language to be built on solid concepts of what we
have learned from the mistakes.*

···

A: When an object is nil, swift syntax itself changes to try to protect the

_______________

Sincerely,
Jason

On Sat, Jan 6, 2018 at 8:07 PM, Javier Soto <javier.api@gmail.com> wrote:

Why isn't that a problem today? Like I showed in that example, that's
undefined behavior and will potentially result in a bug (or even a crash if
instead of an int you end up with an unexpected nil pointer)

On Sat, Jan 6, 2018 at 4:47 PM Jon Shier <jon@jonshier.com> wrote:

Which isn’t a problem right now, AFAICT. Apps compiled under older SDKs
continue to work fine (sometimes better than when compiled under the new
SDK, as the older one avoids new bugs). My question is about how that
compatibility is accomplished today and how and why the Obj-C and Swift
cases are apparently different here.

Jon

On Jan 6, 2018, at 6:12 PM, Javier Soto <javier.api@gmail.com> wrote:

What doesn't happen today? The issue is not when they ship a new SDK:
When rebuilding your app against it, you'll get a warning for a missing
case. The problem is when running the app against a newer iOS version with
a newer version of the SDK where the enum has a new case.
On Sat, Jan 6, 2018 at 3:10 PM Jon Shier <jon@jonshier.com> wrote:

Except it clearly doesn’t happen today when Apple ships new SDKs.
Obviously there’s an alternate mechanism used in that case. I’m just
curious what it is and why Swift so desperately needs an alternative.

Jon

On Jan 6, 2018, at 5:49 PM, Javier Soto <javier.api@gmail.com> wrote:

This is very much an issue in Obj-C today. If you have an NS_ENUM
defined with cases A, B, and C, this switch is correct:

int foo;
swith (e) {
case A: foo = 0; break;
case B: foo = 1; break;
case C: foo = 2; break;
}

(Note the lack of a default case)

If that enum is defined in a framework and it changes after the app is
compiled (like it's the case with Apple frameworks), then that code
produces no warning, yet the foo variable will have a garbage value
(undefined behavior, but as far as the compiler can tell at compile time
your code is fine)

Adding a default clause to that switch has the downside of not getting
warnings for new added cases, like has been discussed before, which is very
useful.
On Fri, Jan 5, 2018 at 7:11 PM Jon Shier via swift-evolution < >>> swift-evolution@swift.org> wrote:

At this point I think it might be useful to outline how binary
compatibility works for Objective-C on Apple platforms right now. As an app
developer I’m not intimately familiar with what happens when you run an app
compiled with the iOS 10 SDK on iOS 11. Are there just runtime checks to
call old code paths or something else? The more this thread goes on the
more confused I get about why Swift would have this issue while it doesn’t
appear to be one for Obj-C. If an enum adds a case now, I don’t have to
care until I recompile using the new SDK. Is the intention for Swift to be
different in this regard?

Jon Shier

On Jan 5, 2018, at 6:41 PM, Jordan Rose via swift-evolution < >>>> swift-evolution@swift.org> wrote:

On Jan 3, 2018, at 00:54, Jason Merchant via swift-evolution < >>>> swift-evolution@swift.org> wrote:

Is it hard to imagine that most everyone can get what they want and
keep the syntax clean and streamlined at the same time? Without any "@"
signs or other compiler hints?

For what it's worth, the original version of the proposal started with
a modifier (a context-sensitive keyword, like 'final'), but the core team
felt that there were a lot of modifiers in the language already, and this
didn't meet the bar.

"Rather, we are how to enable the vendor of a nonexhaustive enum to add

new cases without breaking binaries compiled against previous versions"

When an enum changes, and the change causes the code to break, the user
can be presented with migration options from an automated IDE tool. In what
specific way does this not solve the issue about having to upgrade your
code when using someone else's code library? This very notion implies your
disgruntled about doing work when things are upgraded, is that really what
this fuss is all about?

A well written language interpreter and auto-tooling IDE would not need
hints embedded in the code syntax itself. Migration hints from version to
version should not be a part of either the past or future version of the
code library.

Thanks for bringing this up! Unfortunately, it falls down in practice,
because if there's a new enum case, *it's unclear what you want to do
with it.* If you're handling errors, it's not obvious that the way
you've handled any of the *other* errors is appropriate. In the
(admittedly controversial) SKPaymentTransactionState case, none of the
existing code would be appropriate to handle the newly-introduced
"deferred" case, and nor could StoreKit provide "template" code that would
be appropriate to the client app.

In any case, though, the key point on this particular quoted sentence
is "without breaking *binaries"*. Any such change must be valid
*without* recompilation, and indeed without any intervention from the
developer or an IDE, because that's what happens when the user updates
their OS.

Jordan

...

I don't expect the community to agree on language grammar, but the
common sense here on how to achieve the intended goals seems to be out of
wack.

If someone can present a clear logical statement as to how an automated
migration tool behind the scenes in the IDE to handle all your versioning
worries, does not make this whole discussion about adding more convoluted
syntax additions irrelevant, I'd love to hear it.

___________________

Sincerely,
Jason

On Tue, Jan 2, 2018 at 12:36 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 2, 2018 at 12:11 PM, Jason Merchant via swift-evolution < >>>>> swift-evolution@swift.org> wrote:

I think this whole thing has been unnecessarily convoluted. As a
result, the majority of the replies are rabbit holes.

In my opinion, the true root of the concept in question is as follows:

*A list of something is desired:*
1 - Pancake
2 - Waffle
3 - Juice

*Developer wishes to be able to:*
*A)* Add new things to the list of choices in the future as they
come up with new ideas
*B)* Sometimes select one of the choices to be chosen as the normal
choice if no choice is made by the user

A and B are *separate desires*. In some circumstances a developer
may want to add a new choice and make it the normal choice when there was
no normal choice was clarified before.

I don't think this is an accurate summary of the problem being tackled
here. Rather, we are how to enable the vendor of a nonexhaustive enum to
add new cases without breaking binaries compiled against previous versions.
There is little here to do with what a "default" should be. Indeed, it is
an explicit design decision of Swift not to support types having an
implicit default value.

____________________

*Part 2:*

After this simple desire is clear, there should be two discussions:
*A)* In a text only coding language, what would we like the syntax
to look like? (Without regard to past-bias. What should it really be,
forget what mistaken design choices were made in Swift in the past)
*B)* How do we approach making this happen behind the scenes?

*Bonus:* Given that some of us have changed our approach to
programming significantly beyond text based coding, and into more dynamic
mediums of programming in other niches, and even here and there in Xcode -
I would recommend considering how the IDE would show a modern version of
this concept. I feel too often that Swift design syntax has a *lack
of awareness between the distinctions of what the IDE should do, as opposed
to what the syntax of the language should be*, and what should be
handled behind the scenes by automated tooling.

_____________________

*My opinion*, in answering the above questions is in preference to a
simple easy to read and write syntax, something like the following:

choices Breakfast {
    Pancake, *Waffle*, Juice
}

If a "default" choice is desired, it is obvious to me that I would
select the choice from the IDE, and it would be visually indicated that it
was the default.

When changes occur, whether new choices are added, old ones are
removed or changed, or a default is added, changed, or removed - a behind
the scenes automated tool analyzes the changes and presents migration
options through the IDE.

_____________________

Sincerely,
Jason

_______________________________________________
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

--
Javier Soto

--

Javier Soto

--

Javier Soto

I'm not sure how this solves the problem. We need to know whether an enum may grow new cases or not, a concept that doesn't exist in Swift today. This is most interesting for enums in "libraries with binary compatibility concerns", but is also interesting for libraries that don't have such concerns, like source packages. Source packages don't use availability annotations.

We definitely need to be able to mark when enum cases were introduced, but the language already supports that, at least for libraries that are shipped with an Apple OS.

As for the name, I agree that 'exhaustive' isn't great. The best suggestion we got from the initial review was 'frozen', and that's what I'm putting in the next revision of the proposal.

Jordan

P.S. I'm not sure where you got "versioned" from. The attribute currently spelled '_versioned' in the Swift compiler is equivalent to what SE-0193 <https://github.com/apple/swift-evolution/blob/master/proposals/0193-cross-module-inlining-and-specialization.md&gt; calls 'abiPublic', at least in this revision. (I'm sorry to say I haven't caught up on all the discussion there, so I don't know if that's going to be spelled some other way.)

···

On Jan 9, 2018, at 08:52, Jon Gilbert <swiftevolution@jongilbert.com> wrote:

Having reviewed much of the commentary on this proposal, I keep coming back to the same thought: why not use @versioned and @available keywords for this instead of some concept related to “exhaustive”?

The issue here is not whether a given enum is “exhaustive” over the enumerated problem space; it’s whether the developer wants to alter the enum in the future without breaking ABI compatibility.

If you call it “exhaustive” then it’s misleading, because all enums at a given moment in time can be switched over exhaustively. This will just confuse folks.

Since versioning is really the main goal, why not use the same annotations for versioning enums as are used for versioning everything else?

@versioned
enum MyError: Error {
@available(OSX, deprecated:10.11, message: "this error case is going away in 10.12")
case BadThingHappened

@available(forever)
case ReallyBadThingHappened
}

etc.

That way you could have some cases get removed in the future as well as added, and you won’t confuse people by talking about “complete” or “exhaustive”, which are terms that are too closely coupled with the meaning and application of a given enum to which they refer.

It would be best to use terms that just say what they mean. We are talking about versioning APIs to keep ABI stability? Use @versioned and @available like everywhere else.

Or is there a compelling reason this cannot be done? I read much of the arguments here but didn’t see any mention of @versioned... maybe it’s iOS Mail app thinking I was looking for an email address that contains @versioned? ;D

Jonathan

We seem to agree that, by virtue of not supporting use in a pattern and being placed at the end, the feature is a flavor of default. I’m still not sure I understand why you believe it should not be a flavor of default going forward.

You still haven’t answered my question, though—what’s the use case for the feature you propose?

My use case would be distinguishing between compile time known cases vs “future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not deal breaker, just a different style I’d like to see supported in the future.

I'm still not sure I understand. How can the machine know that it's dealing with an unknown case without first checking if it matches any known case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the compromise, but I would prefer it trigger only on an actual unknown case (as opposed to acting like default). I like to break failure cases out at the top when possible. I don’t see any good reason not to support that style.

To answer your question, in the naive sense, it basically is the same question as asking if it is a known case (and then taking the inverse). That doesn’t mean actually checking each case separately though. For example, if the enum cases are internally represented as an unsigned integer, and they are all together in a block, the compiler could simply check that it is greater than the max known value. You could probably even do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax?

I am not sure I understand what you are asking. There isn’t additional syntax. We are just arguing over the name + behavior of ‘unexpected:’. You want it to behave like ‘default’ and I am saying that stops the use case I mention above.

What do you gain from writing the unknown case first?

I know where to look for the failure cases. I also tend put a bunch of guard statements near the beginning of a function. It is just a programming style.

With my behavior of ‘unexpected:’ you can put it wherever you want. Why limit that by forcing it to go at the end?

Isn't this basically the same thing as asking for the ability to write the default case first, a frequently suggested and rejected syntax addition?

No. I don’t think I have ever heard that asked for, but putting default in a different place has a different meaning. The way I read a switch statement anyway is that it tries each case until it find one that matches. Default matches everything, so it has to go at the end (since it will always match and nothing afterwards will be tried).

Having ‘unexpected:’ also match known/expected cases is problematic as a mental model. I think that is just an artifact of the original proposal using default. There is no reason 'unexpected:’ should have to handle known cases as well… let’s just have it trigger on unexpected ones.

Thanks,
Jon

···

On Jan 4, 2018, at 11:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:
On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

We seem to agree that, by virtue of not supporting use in a pattern and
being placed at the end, the feature is a flavor of default. I’m still not
sure I understand why you believe it should not be a flavor of default
going forward.

You still haven’t answered my question, though—what’s the use case for
the feature you propose?

My use case would be distinguishing between compile time known cases vs
“future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a
distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not
deal breaker, just a different style I’d like to see supported in the
future.

I'm still not sure I understand. How can the machine know that it's
dealing with an unknown case without first checking if it matches any known
case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the
compromise, but I would prefer it trigger only on an actual unknown case
(as opposed to acting like default). I like to break failure cases out at
the top when possible. I don’t see any good reason not to support that
style.

To answer your question, in the naive sense, it basically is the same
question as asking if it is a known case (and then taking the inverse).
That doesn’t mean actually checking each case separately though. For
example, if the enum cases are internally represented as an unsigned
integer, and they are all together in a block, the compiler could simply
check that it is greater than the max known value. You could probably even
do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax?

I am not sure I understand what you are asking. There isn’t additional
syntax. We are just arguing over the name + behavior of ‘unexpected:’.
You want it to behave like ‘default’ and I am saying that stops the use
case I mention above.

Cheyo said he wants “unexpected case” to work in pattern matching, as well
as a new “case *” that is distinct from “case _”. This is additional
syntax. When asked what the use case was for these suggestions, he said he
wants to distinguish between known and unknown cases at the beginning of
the switch.

What do you gain from writing the unknown case first?

I know where to look for the failure cases. I also tend put a bunch of
guard statements near the beginning of a function. It is just a
programming style.

With my behavior of ‘unexpected:’ you can put it wherever you want. Why
limit that by forcing it to go at the end?

As pointed out earlier (by one of the core team members, I think),
meaningful resilience would mean that the unexpected or unknown case should
have useful work executed at runtime; the intention is that the user
*shouldn’t* be treating it as a runtime “failure case,” as it effectively
makes adding an enum case a change that is incompatible with existing
binaries (i.e., not terribly resilient).

As you and I seem to agree, reaching an unexpected case requires at least
notionally considering which cases are expected in the first place. This is
the dictionary definition of a default, and Swift usage is to put the
default case at the end of a switch statement. Adding new syntax as Cheyo
suggests to enable putting it elsewhere, merely for “style,” doesn’t seem
to pass the bar for new syntax, nor is it consistent with existing Swift
usage.

Isn't this basically the same thing as asking for the ability to write the

default case first, a frequently suggested and rejected syntax addition?

No. I don’t think I have ever heard that asked for,

It has been asked for more than once on this list.

but putting default in a different place has a different meaning. The way

I read a switch statement anyway is that it tries each case until it find
one that matches. Default matches everything, so it has to go at the end
(since it will always match and nothing afterwards will be tried).

Having ‘unexpected:’ also match known/expected cases is problematic as a
mental model. I think that is just an artifact of the original proposal
using default. There is no reason 'unexpected:’ should have to handle
known cases as well… let’s just have it trigger on unexpected ones.

I don’t think anyone is proposing that the unexpected or unknown case
should also match expected cases. Where did you see such a suggestion?

Thanks,

···

On Fri, Jan 5, 2018 at 03:11 Jonathan Hull <jhull@gbis.com> wrote:

On Jan 4, 2018, at 11:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:
On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com> wrote:

On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com> >>> wrote:

Jon

I'm going to repeat this from my reply to Cheyo earlier: I really, really don't want recompiling code against a different version of the library to pick a different case than it did before.

(This might be a weak argument since overload resolution, protocol conformance checking, etc can end up picking a different declaration than it did before. But I would hope that the overloads or alternate protocol witnesses at least "do the same thing" in normal situations, if possibly more efficiently. I wouldn't expect that for `unknown case` vs. `default` if you actually had both of them.)

The reason `unknown case` has to match known cases is, again, for source compatibility. If the compiler only produces a warning, rather than an error, when you're missing a case, it has to do something if that case comes up. The most reasonable thing for it to do (in the absence of a separate `default`) is for that to go to the `unknown case`, just like it would have with the last version of the app. That's why I chose to associate it with `default` in the proposal, not the other way around.

Jordan

···

On Jan 5, 2018, at 00:11, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 4, 2018, at 11:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

We seem to agree that, by virtue of not supporting use in a pattern and being placed at the end, the feature is a flavor of default. I’m still not sure I understand why you believe it should not be a flavor of default going forward.

You still haven’t answered my question, though—what’s the use case for the feature you propose?

My use case would be distinguishing between compile time known cases vs “future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not deal breaker, just a different style I’d like to see supported in the future.

I'm still not sure I understand. How can the machine know that it's dealing with an unknown case without first checking if it matches any known case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the compromise, but I would prefer it trigger only on an actual unknown case (as opposed to acting like default). I like to break failure cases out at the top when possible. I don’t see any good reason not to support that style.

To answer your question, in the naive sense, it basically is the same question as asking if it is a known case (and then taking the inverse). That doesn’t mean actually checking each case separately though. For example, if the enum cases are internally represented as an unsigned integer, and they are all together in a block, the compiler could simply check that it is greater than the max known value. You could probably even do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax?

I am not sure I understand what you are asking. There isn’t additional syntax. We are just arguing over the name + behavior of ‘unexpected:’. You want it to behave like ‘default’ and I am saying that stops the use case I mention above.

What do you gain from writing the unknown case first?

I know where to look for the failure cases. I also tend put a bunch of guard statements near the beginning of a function. It is just a programming style.

With my behavior of ‘unexpected:’ you can put it wherever you want. Why limit that by forcing it to go at the end?

Isn't this basically the same thing as asking for the ability to write the default case first, a frequently suggested and rejected syntax addition?

No. I don’t think I have ever heard that asked for, but putting default in a different place has a different meaning. The way I read a switch statement anyway is that it tries each case until it find one that matches. Default matches everything, so it has to go at the end (since it will always match and nothing afterwards will be tried).

Having ‘unexpected:’ also match known/expected cases is problematic as a mental model. I think that is just an artifact of the original proposal using default. There is no reason 'unexpected:’ should have to handle known cases as well… let’s just have it trigger on unexpected ones.

We seem to agree that, by virtue of not supporting use in a pattern and being placed at the end, the feature is a flavor of default. I’m still not sure I understand why you believe it should not be a flavor of default going forward.

You still haven’t answered my question, though—what’s the use case for the feature you propose?

My use case would be distinguishing between compile time known cases vs “future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not deal breaker, just a different style I’d like to see supported in the future.

I'm still not sure I understand. How can the machine know that it's dealing with an unknown case without first checking if it matches any known case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the compromise, but I would prefer it trigger only on an actual unknown case (as opposed to acting like default). I like to break failure cases out at the top when possible. I don’t see any good reason not to support that style.

To answer your question, in the naive sense, it basically is the same question as asking if it is a known case (and then taking the inverse). That doesn’t mean actually checking each case separately though. For example, if the enum cases are internally represented as an unsigned integer, and they are all together in a block, the compiler could simply check that it is greater than the max known value. You could probably even do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax?

I am not sure I understand what you are asking. There isn’t additional syntax. We are just arguing over the name + behavior of ‘unexpected:’. You want it to behave like ‘default’ and I am saying that stops the use case I mention above.

Cheyo said he wants “unexpected case” to work in pattern matching, as well as a new “case *” that is distinct from “case _”. This is additional syntax. When asked what the use case was for these suggestions, he said he wants to distinguish between known and unknown cases at the beginning of the switch.

What do you gain from writing the unknown case first?

I know where to look for the failure cases. I also tend put a bunch of guard statements near the beginning of a function. It is just a programming style.

With my behavior of ‘unexpected:’ you can put it wherever you want. Why limit that by forcing it to go at the end?

As pointed out earlier (by one of the core team members, I think), meaningful resilience would mean that the unexpected or unknown case should have useful work executed at runtime; the intention is that the user *shouldn’t* be treating it as a runtime “failure case,” as it effectively makes adding an enum case a change that is incompatible with existing binaries (i.e., not terribly resilient).

As you and I seem to agree, reaching an unexpected case requires at least notionally considering which cases are expected in the first place. This is the dictionary definition of a default, and Swift usage is to put the default case at the end of a switch statement. Adding new syntax as Cheyo suggests to enable putting it elsewhere, merely for “style,” doesn’t seem to pass the bar for new syntax, nor is it consistent with existing Swift usage.

I agree with the new syntax suggestion is premature. I would hope that the proposed runtime "Deriving collections of enum cases” <Deriving collections of enum cases by jtbandes · Pull Request #114 · apple/swift-evolution · GitHub; function also has a compile time version which we can then use to match all the known cases at compile time. I am happy with SE 0192 as it stands now.

···

On Jan 5, 2018, at 4:42 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Fri, Jan 5, 2018 at 03:11 Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Jan 4, 2018, at 11:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:
On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

Isn't this basically the same thing as asking for the ability to write the default case first, a frequently suggested and rejected syntax addition?

No. I don’t think I have ever heard that asked for,

It has been asked for more than once on this list.

but putting default in a different place has a different meaning. The way I read a switch statement anyway is that it tries each case until it find one that matches. Default matches everything, so it has to go at the end (since it will always match and nothing afterwards will be tried).

Having ‘unexpected:’ also match known/expected cases is problematic as a mental model. I think that is just an artifact of the original proposal using default. There is no reason 'unexpected:’ should have to handle known cases as well… let’s just have it trigger on unexpected ones.

I don’t think anyone is proposing that the unexpected or unknown case should also match expected cases. Where did you see such a suggestion?

Thanks,
Jon

Oh, I see… the case would silently change from unexpected to default if they were both included. Hmm. I will have to think on this more.

Thanks,
Jon

···

On Jan 5, 2018, at 3:17 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 5, 2018, at 00:11, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 4, 2018, at 11:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Fri, Jan 5, 2018 at 01:56 Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Jan 4, 2018, at 10:31 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Fri, Jan 5, 2018 at 00:21 Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

On Jan 4, 2018, at 4:37 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Jan 4, 2018 at 19:29 Cheyo J. Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:

We seem to agree that, by virtue of not supporting use in a pattern and being placed at the end, the feature is a flavor of default. I’m still not sure I understand why you believe it should not be a flavor of default going forward.

You still haven’t answered my question, though—what’s the use case for the feature you propose?

My use case would be distinguishing between compile time known cases vs “future only” cases (or unknown cases).

I understand that the feature you propose would allow you to make such a distinction, but again, what is your use case for doing so?

Breaking out early by checking unknown cases first. I admit this is not deal breaker, just a different style I’d like to see supported in the future.

I'm still not sure I understand. How can the machine know that it's dealing with an unknown case without first checking if it matches any known case?

I had the same thought as Cheyo. It isn’t a deal breaker… I like the compromise, but I would prefer it trigger only on an actual unknown case (as opposed to acting like default). I like to break failure cases out at the top when possible. I don’t see any good reason not to support that style.

To answer your question, in the naive sense, it basically is the same question as asking if it is a known case (and then taking the inverse). That doesn’t mean actually checking each case separately though. For example, if the enum cases are internally represented as an unsigned integer, and they are all together in a block, the compiler could simply check that it is greater than the max known value. You could probably even do a bit mask comparison in some cases...

These are obvious optimizations, but why does this require new syntax?

I am not sure I understand what you are asking. There isn’t additional syntax. We are just arguing over the name + behavior of ‘unexpected:’. You want it to behave like ‘default’ and I am saying that stops the use case I mention above.

What do you gain from writing the unknown case first?

I know where to look for the failure cases. I also tend put a bunch of guard statements near the beginning of a function. It is just a programming style.

With my behavior of ‘unexpected:’ you can put it wherever you want. Why limit that by forcing it to go at the end?

Isn't this basically the same thing as asking for the ability to write the default case first, a frequently suggested and rejected syntax addition?

No. I don’t think I have ever heard that asked for, but putting default in a different place has a different meaning. The way I read a switch statement anyway is that it tries each case until it find one that matches. Default matches everything, so it has to go at the end (since it will always match and nothing afterwards will be tried).

Having ‘unexpected:’ also match known/expected cases is problematic as a mental model. I think that is just an artifact of the original proposal using default. There is no reason 'unexpected:’ should have to handle known cases as well… let’s just have it trigger on unexpected ones.

I'm going to repeat this from my reply to Cheyo earlier: I really, really don't want recompiling code against a different version of the library to pick a different case than it did before.

(This might be a weak argument since overload resolution, protocol conformance checking, etc can end up picking a different declaration than it did before. But I would hope that the overloads or alternate protocol witnesses at least "do the same thing" in normal situations, if possibly more efficiently. I wouldn't expect that for `unknown case` vs. `default` if you actually had both of them.)

The reason `unknown case` has to match known cases is, again, for source compatibility. If the compiler only produces a warning, rather than an error, when you're missing a case, it has to do something if that case comes up. The most reasonable thing for it to do (in the absence of a separate `default`) is for that to go to the `unknown case`, just like it would have with the last version of the app. That's why I chose to associate it with `default` in the proposal, not the other way around.

Jordan

Hopefully this isn't too late, but I'd rather would burn the unknown keyword than having this strange combination of two keyword unknown case where I personally am under impression that I can write something like unknown case .eatenByPet:.

Is it maybe possible to warn everyone in Swift 4.1 (if this proposal is accepted) that a label named unknown will be disallowed and provide a fix-it for that?

unknown ----(after fix-it)----> `unknown`

Similar to how it currently is with default as a label. Then in Swift 5 this feature can be introduced as simple as:

switch instance {
case .a:
   // ...
case .b:
   // ...
unknown:
   // ...
}

This is my favorite from this proposal.

1 Like

Re: unknown as reserved label: It's not impossible, but I'm not sure I'd consider it worth it. The proposal's trying pretty hard not to break Swift 4 compatibility.

One option would be to allow that spelling but only in Swift 5 mode.

I would enthusiastically support unknown: but only in Swift 5 mode. unknown case feels slightly clunky.

4 Likes

Swift 5 mode is fine by me. It makes things nice and clean while not loosing the swiftiness. :)

The label feature is used pretty rarely to begin with. ‘unknown’ seems a very odd choice of name on top of that. Combine that with it having to be in a switch statement to cause any ambiguity. I'd be very surprised if this does break anything.

Compared to the significant source breakage of switch statements this proposal would create, it seems excessively cautious. We should really just go with unknown.

2 Likes

Premise: I can't understand if this review actually finished or not. If it is you can ignore this post :)

Personally the only good solution I see is to add the ability to use versioning per-case.

When updating a library (be it OS-provided or 3rd party) to the next major version, that's allowed of being source-breaking. If an application starts supporting that next major version, it might have to adapt the code to work with that. The same can happen with functions, which can become deprecated or unavailable, I don't see why not with enum cases.

What it would mean for the library developer, though, is that when you version a case you can use that only within other versioned scopes that fit with it. I don't know if this can become a maintenance hell of sorts though... probably not different from what versioning causes to other language features though.

Also I'd like to add that a "non exhaustive" enum could be implemented with protocols, if only those had a way to define where a type can conform to it (but this is another discussion). Other languages, like Scala, basically use protocols instead of enums since they don't have associated values on cases, and have the option to make the protocol "exhaustive" or not. If we do let enums be non exhaustive then the only semantical difference between these and enums would be if the consumer is allowed to extend it or not. I'd prefer to move this choice to protocols and keep enums as-is.

I'm not sure the "unknown" case deserves special label-level syntax on its own. Even if it does only make sense at the top level of a switch, it feels to me like a feature that shouldn't really see much use in practice. Since @frozen is itself an attribute, it also feels asymmetric to give unknown first-class syntax. Someone (Discourse search is stumping me, apologies) raised the idea of #unknown or some other token that could be parsed in pattern position, and that feels more appropriate to me, since it falls into the same bucket of "special compiler behavior" as other #-decorated things. (If # comes to mean "macro" someday, you could say #unknown expands to _ while also doing the partial-exclusivity analysis in some way for future Swift evolution to decide…)

1 Like

I believe #unknown was suggested by @Chris_Lattner3

case #unknown is the best solution I've seen for this issue, by having it be a pattern, +1 for that.

1 Like