Correct. The isIdentical(to:) method for value types can — and maybe should — remain a "niche" API. It's a specialized performance hook intended for library maintainers or product engineers that have thought through and understand the performance tradeoffs involved. Promoting this API to an operator brings a lot of visibility… and I don't believe that's what we want or need.
Presumably there would exist a view component in this app that performs some transformation on the source of truth. This could be a ContactDetailPage with a Form that gives users the option to Toggle the isFavorite property. But I wouldn't really see a lot of impact from actually writing out that component in the proposal. I think it's fine to wave the hands over that part.
I do want the SwiftUI example to remain in the proposal. But I wouldn't be totally opposed to adding one or two sentences here to say something along the lines of:
And another component in this application is updating these Contact values which then could lead to FavoriteContactList recomputing its favorites values.
But I definitely don't want to get all caught up in some "SwiftUI data flow" conversation here in this proposal where we start to debate Flux-Redux against MV*. I really don't think this proposal is the right time or place for any conversation about that. Hence my reluctance to actually show how these Contact values might be transformed.
Generally I like the concept. But I’m not sure about the naming.
The definition says
We propose new isIdentical(to:) instance methods to concrete types for quickly determining if two instances must be equal by-value.
I read “is identical” as the 2 values have the exactly same representation.
-0.isIdentical(to: 0) returning true from fast path formally passes the axioms, but is unsound.
Not mentioning that it could collide with true isIdentical(to:) that some folks might have (or even the standard library in the future). It is reasonable to have Double.nan.isIdentical(to: .nan) returning true, yet it would violate the equality axiom.
If it is supposed to “quickly determine if two instances must be equal by-value” I would rather spell it in that way. If it is “niche” functionality, spell it. Even arguably awkward isFastEqual(to:) sounds better to me. Or come up with some agreeable operator, like \== or whatever.
There exists a long-standing prior art in Standard Library that "equal" representations are not always "identical-indistinguishable" representations:
This has shipped for years in Standard Library. There does not seem to ever have been any big controversy about this… but if you have the opportunity to volunteer with more research please respond back if you find out any interesting details about the evolution of SetAlgebra that might help us here.
Before I get too far in the weeds with this answer… it might be helpful to slow down and also review some goals with the proposal:
Primary: Our most important goal is to define a set of isIdentical(to:) methods on concrete types. This means that every type in this proposal should be ready to define its public semantics and guarantees so that library maintainers can then build impactful implementations.
Secondary: A less important goal is to define an "abstract" set of "informal" guidelines that are not only an umbrella blanket over the concrete types in this proposal but might also cover future concrete types in Standard Library — and any library — that attempts to add isIdentical(to:).
Tertiary: A "nice to have" goal is to then attempt and "backdeploy" that abstract set of guidelines to existing types like Span that already shipped isIdentical(to:)before this proposal.
So if we want to have a discussion about what "is identical" really means… it might be helpful then to slow down if we are attempting to go too far down the road of an "abstract" protocol definition that we expect all future concrete types to adopt. That is not — and should not be — a primary goal of this proposal.
But if we did want to discuss some more abstract concepts behind these ideas I collected some more thoughts here that might help:
I briefly thought about putting this down as an "appendix" on the proposal… but I don't feel like I have strong enough opinions to attempt to codify this all down to some kind of "canon" treatise that would then serve as a prior art for future proposals. But in general my thoughts here so far are:
Copies are representations that are "identical-indistinguishable".
Copies are representations that the compiler can be free to insert or remove on your behalf with zero expected changes on the surface meaning of your program.[1]
Representations that are "identical-indistinguishable" are "interchangeable-substitutable".
Representations that are "identical-indistinguishable" are not necessarily copies.
Representations that are "interchangeable-substitutable" produce the same behavior in a program or algorithm that depends on the value properties of this representation. This does not include object identity or any other public state that is not considered as part of the public value.
Representations that are "interchangeable-substitutable" are not necessarily "identical-indistinguishable".
This possible missing piece here so far for me is to what extent we can attempt to define what it means to "be identical" as a "weaker" guarantee than what it means to be "a copy". But again… this all should not be a primary goal of this proposal. Our primary goal should be to define the semantics and guarantees of isIdentical(to:)on concrete types. We feel the existing proposed header doc comments are strong enough promises to make for an impactful API and I am not yet persuaded to change my opinion about that.
We are not considering exposing identity at this time. A potential future direction that exposes identity should not block this existing proposal from shipping. Here are some more thoughts on that:
Here I would respectfully disagree with the claim that this proposal introduces or "coins" a term of art. I believe it is more appropriate to claim this proposal leverages an existing term of art.
I already collected some thoughts around this idea:
I would not agree that this proposal introduces "identical" as a term of art… I believe this prior art shows that we already have used this term of art across Standard Library for years. And this wasn't "fan fiction" Swift… this was legit canon Swift documented in Standard Library and Evolution.
Here I believe this maybe comes back to some of the "what is a copy" questions I had. We currently define a copy to be a representation that is identical. What's not clear is the other direction: if we explicitly then formally define an identical representation to also be a copy. Since we are lacking that second definition then I believe it is fair to assume that "identical-indistinguishable" does not imply a representation is a legit copy.
From that framework it looks like a representation that is a copy makes a stronger guarantee than a representation that is identical-indistinguishable. When I see hasSameRepresentation(as:) this looks to me like we are suggesting our library maintainers should think of this operation as "testing for a copy". I don't believe that is necessary or desirable.
For a more concrete example suppose a library maintainer is thinking about adding isIdentical(to:) on a String. This is not necessarily a Standard Library String… just think of this as "some" String. This String can be saved inline by-value or "outline" buffer by-reference. The decision to save inline or buffer is an opaque implementation detail not revealed as a public state.
Suppose we then attempt to call isIdentical(to:) on self and other. Each String can be inline or buffer.
Let's start with the situation where self and other are both buffers. Here we compare the buffer reference for identity equality and return true if our pointers are equal. This is an O(1) operation. Simple.
Suppose self and other are both inline. If our library maintainer enforces that inline strings are never more than k bytes of data we can then compare these two values directly. Because the k value here is fixed we can still consider this to be an O(1) operation. Simple.
Suppose now one value is inline and one value is a buffer. What now? Because our inline strings are never more than k bytes of data we could choose to compare the inline value against the first k bytes referenced from our buffer reference pointer. This is still a constant O(1) operation and we can return true to indicate these values are identical. A library maintainer could also make the argument that these two values should not compare as identical… but I would think of it as totally legit to go ahead and perform this comparison since we do not break the semantic performance guarantees of the operation.
Take that previous example but instead assume we are implementing hasSameRepresentation(as:). Now it's implying something stronger than "is identical". If we attempt to compare one value that is inline and one value that is buffer it now looks like our library maintainer has far less flexibility. The name of the API is telling our library maintainer what the implementation should be. Even if our library maintainer wanted to compare the first k bytes of a buffer to an inline value the name of the API actively discourages this flexible thinking.
This could be a clue for our previous discussion and unanswered questions: under what situations is a representation identical-indistinguishable and also not a valid copy? Here I think we're beginning to actually see where this subtle distinction might be.
I'm not completely sure there's a strong community consensus that adding more methods to Optional is even desirable. For example:
A product engineer might choose to implement Optional identical tests with a free function. I don't personally have a very strong opinion which approach is better to the extent I am ready to ship that opinion in Standard Library.
We don't discuss "backdeployment" too much in library evolution… but as of right now I do believe the plan is to implement these methods with alwaysEmitIntoClient or backDeployed wherever possible. So AFAIK there could be a legit impact to binary footprint if we choose to include a method on Optional.
I'm not completely opposed to adding the Optional methods. If there is a strong consensus here in proposal review I'm not going to push back strongly on that. But as of right now I see more arguments in favor of not shipping the Optional methods in Standard Library at this time.
I'm having trouble tracking this feedback. Given that our primary goal with this proposal is to focus on a well-defined set of concrete types, could you please help me understand what your argument is in favor of renaming isIdentical(to:) to isKnownIdentical(to:) on the concrete types from the proposal?
I'm not sure I follow this feedback. Are you directing this feedback at the concrete types in the proposal? Which concrete types are failing to explain the relationship to value-equality?
Our proposal is not adding isIdentical(to:) on Int and this is specifically called out in the proposal as a type we do not recommend for isIdentical(to:).
Our proposal is not adding isIdentical(to:) on Float or Double. Neither of these types are good candidates for isIdentical(to:).
Our proposal also specifically mentions that nan is an "exceptional" value that technically violates our equivalence relations as a known edge case. This is consistent with long-established precedent from Standard Library.
Our isIdentical(to:) on Arraydoes not require the Array to be Equatable. I do not agree that putting "equal" in the name of the API is the correct decision.
As discussed previously I am pushing back on any feedback requesting an operator for this proposal. This can and should be a named method and nothing I have seen so far has persuaded me to change my opinion about that.
I understand that standard library is not adding isIdentical(to:) to the types in my example. Int and Double literals were just perfect to concisely illustrate the problem that other types might suffer from. What I’m trying to say is that I feel like self-explanatory isIdentical(to:) is being hijacked for different meaning.
I’m sure someone can come up with a much better name than I can. But isIdentical(to:) just doesn’t sound right to me given how it’s defined.
Perhaps mustBeEqual(to:)?
If mustBeEqual(to:) semantics would use true isIdentical(to:) for result, it is perfectly fine to have such function alongside and call it from the mustBeEqual(to:).
I don't completely follow this feedback. You are saying that isIdentical(to:) is self-explanatory? Why does this imply that you are asking to change the name if it is already self-explanatory?
This specific name was discussed in a pitch thread and we decided no for that:
As discussed in my previous comment we are not enforcing that all isIdentical(to:) methods must only be on concrete types that adopt Equatable. We do not want to put "equal" in the name of this API.
We have documented years of prior art in Standard Library and across Swift libraries that show isIdentical(to:) is already being used for the purpose we are proposing here for new types. This is the best name and the right name and I have not seen any compelling reason to change my opinion about that.
We propose new isIdentical(to:) instance methods to concrete types for quickly determining if two instances must be equal by-value.
nonEquatable.mustBeEqual(to: otherNonEquatable) can always return false and formally it is fine with me. It is arguably a bit awkward but in the same sense as “We propose new isIdentical(to:) instance methods to concrete types for quickly determining if two instances must be equal by-value (that doesn’t need to be Equatable).”
I’m not saying that there is a perfect consensus for what isIdentical(to:) means, but it will be rather about underlying representation than about quick determination whether the two values must be equal.
We explicitly address why an implementation that always returns false from isIdentical(to:) is not allowed:
a.isIdentical(to: a) is always true (Reflexivity)
An implementation of isIdentical(to:) that always returns false would not be an impactful API. We must guarantee that isIdentical(to:)can return true at least some of the time.
I agree, if it is isolated to a true isIdentical(to:).
My whole point discussing here is to say that I think of mustBeEqual(to:) as of different from isIdentical(to:). And that I’m under impression that isIdentical(to:) is being hijacked to be merged with mustBeEqual(to:) under single set of rules, with acrobatics to get away with it.
I think these 2 should be separate concepts with their own rules.
isIdentical(to:): This is kind of established already in the sdtlib. I don’t think it should be required to always return on fast path, or be forbidden for types that can contain “exceptional values” which is just an excuse to make mustBeEqual(to:)working.
mustBeEqual(to:): The purpose of this proposal. To establish an API for returning on fast path whether the 2 values would return true on ==. And yes, in most cases the implementation would be just calling isIdentical(to:).
Respectfully, the feedback that is being delivered here has done nothing to persuade me to change my opinion. The proposal is for a set of methods named isIdentical(to:). We are not adding mustBeEqual(to:) to this proposal at this time.
If you choose to deliver more feedback on this topic… some advice might be to not worry so much about repeating the opinion that isIdentical(to:) is the wrong name. Try to think about persuading the proposal author that isIdentical(to:) is the wrong name. Ideally this persuasion would also address the years of prior art in this space indicating that isIdentical(to:) is the correct name.
That being said… there is nothing stopping a library maintainer or product engineer working outside the Standard Library from shipping their own extension on Array that implements mustBeEqual(to:). That engineer can choose to forward mustBeEqual(to:) to isIdentical(to:) and I have no opinion or desire to block these downstream implementations from happening. If you and your team decide that these are the correct semantics and the correct name for your repo… then go ahead and do it. That's fine.
What I will continue pushing back on is shipping mustBeEqual(to:)directly in the Standard Library. I am strongly opposed to this idea and nothing I have seen here has persuaded me to change my opinion about that.
I like the idea of having a notion of quick determination whether 2 values of same concrete type would result in equal by value. And I like that that it is an “informal” (not concrete) protocol and that it is unrelated to Equatable; and it does so via establishing a method, not operator.
I disagree with the naming. I don’t think it follows the prior art of isIdentical(to:), but is rather an extension that imposes additional rules on how isIdentical(to:)should be implemented in order to provide the functionality that this proposal addresses.
That being said, it is your call. If it is accepted, I’ll respect and follow it. Should I need a method for comparison of underlying representation that would return true, but the values wouldn’t return true for ==, or couldn’t return on fast path, I will name it hasSameRepresentation(as:) instead of isIdentical(to:).
What might help here is to consider the meaning of this API at face-value. The API is called "is identical". The API returns true to indicate self "is identical" to other. This is pretty self-explanatory as we previously both confirmed.
An important — and impactful — consequence of that true value is the implication that self is also equal to other. The well-defined set of types in the proposal have no problem satisfying that semantic guarantee. All we are basically doing here is exposing the existing "fast path" operations that already exist on these types and have shipped for ten plus years.
If there was a specific concrete type in this proposal that you felt was unable to satisfy that semantic guarantee then this is important information to share with us during proposal review. If you can identify some case — other than the previously established exceptions we made for "exceptional" values like nan — where one of these concrete types would be "broken" then please let us know about that.
To be fair, I eventually can't come up with a case that wouldn't break Equatable contract other than by with a superior exception (like the .nan from IEEE-754). Nor can come up with a type that would compare internal representation slower than == on its values.
I think I'm convinced that isIdentical(to:) won't be prone to not fulfill the "must be equal" contract by being "is same representation". With this assumption, isIdentical(to:) is certainly a good name to use.
Thank you for being so patient and helping me realize this.
My only (and minor) concern is that the name isIdentical(to:) is not scary enough. In that regard isKnownIdentical(to:) following the example of isKnownUniquelyReferenced & co could be an option.
If the return type were a tristate (identical/definitely-different/unknown, either an enum or optional Bool) that could also solve this problem, although I suspect the implementations would pretty much never return definitely-different.
My opinion here is I am generally going to discourage engineers from thinking of this API on these concrete types as "same representation". As I was discussing from my previous comments I see an API like hasSameRepresentation(as:) as being a way to test if other is "a copy" of self. But I also see a copy as being a much stronger statement and set of guarantees than what we call an identical-indistinguishable representation:
A representation that is a copy is identical-indistinguishable. A representation that is identical-indistinguishable is interchangeable-substitutable. Therefore a representation that is a copy is interchangeable-substitutable. We already define "equal" values to be representations that are interchangeable-substitutable. I'm not completely sure we can safely go the other direction — that all interchangeable-substitutable representations are also equal — but I'm pretty sure the axioms of interchangeable-substitutable would also imply equality for both directions even if that is not formally made explicit.
From that framework: all we really care about for the purposes of this API on these concrete types is to guarantee identical-indistinguishable. Some identical-indistinguishable representations are not going to be legit carbon copies and this is fine. See the examples listed previously for times a library maintainer might want to return true when two representations have a technically "different" representation but are still "practically" identical.
A "true" hasSameRepresentation(as:) method that does return true if a representation is a legit copy might have some interesting use cases behind it. But that's not what this specific proposal is worried about at this time.
I'm actually sort of still "saving" isKnownIdentical(to:) for the future proposal that puts this directly on Equatable:
TBF… that's not to say that a proposal that isn't even on the calendar for review — and which LSG has explicitly declined to review — would be the only reason to block this proposal from using that name.
Looking at this — or any other debates about different names — keeps coming back to one important detail that none of the reviewers proposing these different names have done a good job addressing: the prior art.
I'm not just some guy standing at the bus stop on Homestead and Wolfe yelling at you about why isIdentical(to:) is the right name. I'm some guy standing at the bus stop on Homestead and Wolfe yelling at you about why isIdentical(to:) is the right name with data to back me up on that.
Some examples of the data and evidence I can bring to the table:
Standard Library has shipped an unsupported-underscored public implementation of isIdentical(to:) on String for years.
Collections has shipped supported public implementations of isIdentical(to:) on concrete types like BigString and Rope for years.
Standard Library shipped a supported public implementation of isIdentical(to:) on Span.
The SE-0447 proposal review came with zero requests to change the name of isIdentical(to:).
I don't want to discourage any engineers from posting here with suggestions for different names. I encourage engineers to post here with suggestions for different names and I will act in good faith to consider alternatives. That does not mean I'm going to make it easy for anyone to change my opinion about this. Disregarding the years of prior art telling us isIdentical(to:) is the right name — without any clear agreement why it should be disregarded — would be an example of me making it easy to change my opinion.
What I really want to see is for any engineer that disagrees with us naming this method isIdentical(to:) to please address the prior art in this space:
Why is this prior art "wrong"? Why does the existence of this prior art not imply that isIdentical(to:) is the right name?
If an engineer proposes a different name for isIdentical(to:) on the concrete types in this proposal, does that same engineer also propose we go back and change isIdentical(to:) everywhere it's already being used for this purpose?
Where was this feedback when Span was being reviewed? Why did no engineer come forward during proposal review and request to change the name of isIdentical(to:)?
What prior art supports a different name for isIdentical(to:)?
Where in Standard Library do we use that different name for the operation we are proposing here?
Where in adjacent Swift libraries do we use that different name for the operation we are proposing here?
Where in other languages or ecosystems do we use that different name for the operation we are proposing here?
To be clear about the process here, Rick, while you are welcome to argue in favor of the name you used in your proposal, the LSG is going to make its own decision based on all the feedback and discussion, and you do not have a formal gatekeeper role as the proposal author.