I don't understand why you're being defensive about this Quincey, I'm not ignoring your thoughts. I have posted multiple times here saying that if the wildcard confusion is a real issue we should be able to locate a legitimate cross-platform availability use-case that proves this.
When an issue is not addressed in the proposal text but comes up during review, the core team has the discretion to ask for the discussion to be incorporated into a revised text and then re-reviewed. As @rockbruno has stated, he's reopened this discussion to work on that revision, and he's laid out the text that he intends to incorporate. From what I can read, the text:
- "define[s] the semantics for
#unavailable
for the platforms not specified" - "clearly explain[s] how those semantics compose with the use of
#available
and other#unavailable
annotations"
Would you agree? Are there some semantics about unspecified platforms that haven't been adequately defined in the revised text? Are there some compositions of annotations whose semantics haven't been adequately explained? Can you think of alternative semantics that would be easier to define or can be explained more clearly?
Let me first address a simpler interpretation of the *
in if #available
. While it's true that you can interpret it as "the minimum deployment target the current platform, if not listed elsewhere", a simpler interpretation is "any other platform". The reader doesn't need to think about "deployment target" at all. For example:
// On iOS 14+, macOS 11+, or on any other platform...
if #available(iOS 14, macOS 11, *) { }
This interpretation is fully consistent with the "minimum deployment target" definition used in the compiler, but I believe it's easier to reason about, and I believe it's more likely to be what users would intuit (if they intuit anything at all.)
Given that interpretation, I'm not sure it makes sense to use a *
at all on #unavailable
. If we have one, it should mean the same thing:
// If we're on macOS <11, or on any other platform...
if #unavailable(macOS 11, *) { }
The *
is important for the #if available
case because it unlocks newer APIs, and it's likely that if you add a new platform, you would want to default to the newer APIs by default. If those APIs aren't available on that platform, the compiler will give you an error, so it's relatively safe to assume that the author would want to use the new APIs by default.
On the other hand, the primary use case I can imagine for if #unavailable
is backward compatibility. For example, in iOS 13, the concept of "Scenes" was introduced, and scene delegates took over many of responsibilities that were formerly in the app delegate, leaving the app delegate with less to do in iOS 13+. Thus, the app delegate might look something like this:
if #unavailable(iOS 13) {
// Do UI setup here. On iOS 13+, this is done in the scene delegate.
}
If #unavailable
is primarily only interested in backward compatibility on a particular platform, then on any unlisted platform, it's likely that users don't need any backward compatibility at all. If you've never released your app for tvOS, you probably don't need backward compatibility for it. It would certainly not be the right behavior to assume that the author wants to use the older APIs when adding a new platform.
Thus, I don't think a *
should be required, because it would be the wrong default in most cases. This leaves us with two options:
-
Default to
false
on any missing platforms// Pre-iOS 13, run this code. For other platforms, skip it if #unavailable(iOS 13) { }
If I am correct that
#unavailable
would mostly used for backward compatibility with older versions of existing platforms, then this would likely be the right behavior in most cases. -
Error on missing platforms.
// Pre-iOS 13, run this code. For other platforms, compiler error if #unavailable(iOS 13) { }
It would be a compiler error to encounter
if #unavailable(iOS 13)
when compiling for a platform other than iOS. When it's time to add support for a new platform, the author would need to modify the check:if #unavailable(iOS 13, macOS 11, tvOS 13) { }
This is safer behavior, in that it forces the author to evaluate the availability at the moment of adding the new platform, similar to the error you might get when using
#if available()
if the new platform has an older deployment target. It does, however, make things more inconvenient when adding a newer platform.
In either case, the *
is undesired. Under my interpretation ("... or on any other platforms"), it's the wrong default. Even if you disagree with that interpretation, it's clear that there's not a consistent interpretation that Swift users will understand, so the *
should be invalid. It does not have a consistent understandable meaning that is easily explained, so we should not allow it.
I'd say frustrated, rather than defensive, perhaps ā frustration because there is, in fact, confusion.
You want proof? OK:
-
Me. I've been confused by the unavailability syntax, though I think I do actually understand what you intend by it now. That doesn't mean it wasn't confusing, but I guess I have to recuse myself as a witness.
-
You. For example here. This is not any kind of criticism. I just note that the struck-through text indicates that it took 2 tries to get it right. At least, this indicates that getting it right is complex, not simple, and this relatively trivial piece of syntax is going to cause confusion if complex.
-
@xwu. As before, here. It may have been momentary, but the recorded reaction was not "You got the right answer by the wrong argument", but "You got the wrong answer".
-
@bjhomer. I think this is a great example of how the
*
is going to be universally understood (outside this thread). Unfortunately, that isn't going to be the actual behavior.
You can clear up confusion, but that doesn't mean that the confusion didn't exist. You can explain a misunderstanding, but that doesn't mean the intent wasn't misunderstood.
I like the interpretation of "...or any other platform"; to me, though, it does mean the same thing as proposed by @rockbruno:
// If macOS 11+ is available, or any other platform is available...
if #available(macOS 11, *) { }
// If macOS 11+ is unavailable, or any other platform is unavailable...
if #unavailable(macOS 11, *) { }
It does not make sense to me how one would arrive at the opposite meaning when using this interpretation. But perhaps Swift users will simply not understand it.
By contrast, to me, I cannot understand how if #unavailable(iOS 13) { }
would be skipped by other platforms: it plainly says that the code should be run if iOS 13 is unavailable, and iOS 13 is clearly unavailable if we're on macOS.
So if there is no consensus that can be achieved here which would not be completely interpreted in the opposite way by someone else, then perhaps this is a sign that the feature should not be added at all.
You're right, that would be confusing. That's a solid strike against my first option.
What about the "error on any other platform" option? This requires the author to be explicit about what they mean.
Basically this is how I feel about it at this point (because Iāve gone in circles myself trying to actually figure out what an unavailability statement is actually saying).
After much experimentation, the only way that I can personally reliably arrive at the correct answer when reading an unavailability condition is to pretend itās an availability condition, and then negate the result I arrive at...
So if we canāt have !#available (and it sounds like we canāt) then Iād rather not see this feature in the currently proposed form.
Also I find that second comment confusing simply because (and I believe you have used this logic in this thread earlier), letās say Iām running macOS 11, Iām on macOS, so surely iOS is not available right? So this statement should be true, right? And now I have failed
And really, the reason I failed is because the second comment should technically be a logical and, not a logical or, because of the negation, but everyone will read it as a logical or, just as you have written it here.
I'm wondering if #unavailable
should only allow one platform parameter. That would match the behaviour of the @available
attribute with the unavailable
parameter:
// These work:
@available(*, unavailable)
@available(iOS, unavailable)
// These don't work:
@available(iOS, *, unavailable)
@available(iOS, macOS, unavailable)
I don't mean to be condescending, but we once again circled back to the beginning without addressing what I asked... There is a theoretical confusion, but will it actually happen in code? Is there someone who really relies on *
for things to work, in code?
This whole discussion for me sounds like saying that computers are a bad invention because they will stop working if you explode them. Surely bad, but who the hell does that? That's what I think we should discover for *
before arguing about whether or not we should change the syntax. In the case of *
: who is using *
in a way that would make the functionality confusing?
I'm not against coming with a different solution, I'm against doing so with no concrete proof besides what we personally think. I think what I intend to achieve here is that ultimately we should have no expectations for *
in terms of functionality, because (I think) no one will ever rely on it. Thus, it would be present simply as a way to make it clear that other platforms are also being considered in the statement, just like for the original #available
.
i.e, If I'm correct about my claims, then we are overthinking an aspect of this feature that no one will every rely on. We made up this problem, it's not legitimate.
My interpretation of the motivation for *
in #available
is that it's there so future platforms are supported automatically (at least that's what the error message is saying if you leave it out). This is already the case for #unavailable
because it's a negative statement so I think the *
could be left out entirely.
That's the ultimate goal, but the feature itself tells you what will happen to all other platforms. As in, #available(iOS 13, *)
in watchOS is equivalent to #available(watchOS minimumTarget)
. This behavior comes from *
, so my worry about removing it is that we would lose this visual component.
If we did this we would probably also have to remove the ability of having multiple platforms in the same statement, which is definitely not a bad idea, but as I mentioned before I would like to make sure we're not dealing with a made-up problem before going to a different path.
I've read the upthreads about *
concerns, but I think the culprit is NOT the *
itself, but the relationship between specified part
and the rest part... (all other platforms *)
which in #available is OR
but in #unavailable should be AND
!
e.g. the logical expression (A or B)
, the negation of it !(A or B)
should be (!A and !B)
.
For #available
and #unavailable
(!#available
in semantic) , #available(macOS 11, *)
means available in macOS 11+ OR all other platforms, but #unavailable(macOS 11, *)
means not available in macOS 11+ AND all other platforms which equivalent to "ONLY available
in ! (macOS 11) (macOS<11)." At the same time #unavailable(macOS 11)
means unavailable
in macOS 11 ONLY
but still maintain availability of all other platforms.
In conclusion, #unavailable(something,*)
means unavailable something AND others...
but #unavailable(something)
means unavailable something ONLY (all others still available)
.
Hope it clear enough to interpret *
in #(un)available
statements ~
What if we support the syntax that omits the *
as a syntax sugar? The thing here is that the wildcard has a technical definition that makes sense for unavailability (minimum target), but from a syntax point of view it looks incorrect because this technical definition is not a developer's first thought. This is essentially what Quincey proposed above but without the *
makeover.
If this makes sense, we could consider #unavailable(iOS 12, *)
as the valid spelling but also allow writing #unavailable(iOS 12)
as the syntax sugar version of it. We would then eat the inconsistency between what's written / what actually happens deep down for the benefit of having a better syntax overall.
What I was trying to say (and maybe I'm wrong) is that the *
in #availability
isn't about the visual component or about making it clear that other platforms have been considered, but to ensure that if more platforms are added to Swift in the future that the condition defaults to available instead of unavailable.
Now with #unavailable
statements, the condition would default to available for future platforms by definition, because all platforms except the listed ones are included. Which would imply that *
isn't needed with #unavailable
.
This would mean that *
has no meaning at all, which doesnāt seem right. It also doesnāt address the issue that #unavailable(iOS 12)
is somehow false
for macOS, nor the issue that some users expect #unavailable(iOS 12, *)
to be somehow true
for macOS.
I think fundamentally the feedback is that users expect unnamed platforms to be available, and it seems that with any spelling whatsoever, that unnamed platforms are unavailable is surprising to some proportion of users. I donāt get it either, but if thatās the case, the issue is inherent to ānegative availabilityā in general rather than the wildcard, and I would agree with @QuinceyMorris then that itās unsolvable.
If you trigger the error in a different platform, you'll get an additional diagnostic asking for the current platform to be mentioned:
error: condition required for target platform 'macOS'
if #available(iOS 12) {
^
But you are not wrong, this was primarily done to support future platforms and we're now going deeper to fit it into another context.
Taking a broader view, whatās actually desired here isnāt a negated check, itās a different version range.
If rethinking #available
was in scope, Iād want to consider something like:
- Allow the existing clause
#available(iOS 13, *)
to be written as#available(iOS >= 13, *)
- Allow the use of
<
to express the reverse.
*
would still mean āevery version of every unnamed platformā.
During the review, I proposed omitting the *, but as @xwu has pointed out, it actually suffers from a similar problem as with the *.
Let's consider the following example:
if #unavailable(iOS 13)
Let's say I'm running iOS 12, of course iOS 13 is unavailable, so this is true.
Now let's say I'm running macOS 11, well, iOS 13 is still unavailable because this is macOS, so this is still true right? Nope, it is false, hence the confusion.
Now let's consider this example:
if #unavailable(iOS 13, *)
As @xwu mentioned earlier, this will be read as "If iOS 13+ is unavailable, or if any other platform is unavailable".
Let's say I am running iOS 12, iOS 13+ is unavailable, any other platform is also unavailable, so this is true.
Now let' say I am running macOS 11, first part: iOS 13+ is unavailable so first part is true, second part: macOS falls into "any other platform" here so second part is false, so overall this is true right? Nope, it's false because as I mentioned earlier, and as @BigSur mentioned, in #unavailable the comma is AND not OR, but everyone will read it as OR.
So asterisk or no asterisk, there is still a very high likelihood of misinterpretation (because it has been demonstrated several times in these threads that even experienced Swift programmers misinterpret these statements).
But the spec list is not a boolean expression, that statement is read as simply āiOS 13 is unavailableā.
I think this is why we're having so much trouble with this, some of us are seeing it like an if expression when it reality it's more comparable to an Obj-C complex macro. To put in short, the specs that talk about other platforms are irrelevant.
I've tried updating the draft to fully describe how the spec list works. Can you tell me if #unavailable(*)
it's still confusing after reading the semantics section?