SE-0215 - Conform Never to Equatable and Hashable

The review of SE-0215 — Conform Never to Equatable and Hashable begins now and runs through June 19, 2018.

The proposal is authored by @mdiep.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager

5 Likes

Yes, yes, a thousand times, yes.

I would love to have Never be treated as a universal bottom type, but Equatable and Hashable are excellent low-hanging fruit that cover a decent chunk of use cases.

8 Likes

It's great to have this.

tl;dr: I'm in favour of this in principle, but it would be the lowest possible priority for me.

What is your evaluation of the proposal?

I have no objection to it in principle. It seems like a logical thing to do although I'd rather that the nettle was grasped properly and you made Never conform to all protocols without type level requirements.

Is the problem being addressed significant enough to warrant a change to Swift?

This is where I have difficulty. How common is it for people to get into situations where not having Equatable and Hashable conformance for Never is a real issue? Sure, a couple of theoretical examples have been provided but are there any concrete cases in the real world where this has been an issue?

I have to say I think the answer to this question is no, for the moment.

Does this proposal fit well with the feel and direction of Swift?

Since the feel and direction of Swift is defined by the Evolution process, this question is somewhat tautological.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal and the discussion thread.

If we accept this, I think we are implicitly accepting Never as a universal bottom type. I mean, I don't see what makes Equatable or Hashable so special with regards to Never (considering that you can never have an instance of it to hash or compare to any other instance).

I would be in favour of that (Never as a universal bottom type). It can be useful for prototyping purposes.

4 Likes

I also support this proposal and generally treating Never as a bottom type.

However, I am a little bit concerned about previous discussion that has suggested that should include conformance to every protocol including those with initializer requirements (for which the compiler would synthesize a trapping implementation). My concern also includes any requirement which can only be met by producing a new instance of Self without also requiring a previously existing instance of Self be provided.

I don't know type theory as well as many others in this community. Does formal type theory include notions that correspond directly to protocols and conformances (especially those with static and initializer requirements)? Or are these notions that are layered on top of formal type theory?

3 Likes

I know you cannot create an instance but calling an initializer does not require you to have an instance. If Never conformed to these protocols it would need to expose the required initializers. That is why it has been suggested that the compiler would synthesize a trapping initializer. When Never is bound in generic code which calls the initializers (this only requires specifying the type, it does not require an instance) a crash would occur. It does not sound like a good idea to me to allow that.

2 Likes

Moderate -1. Never is special because it has no instances - it is uninhabited by design. This means that it can conform to all protocols. I'd rather see a global solution rather than cherry picking these two specific protocols.

Also, in the discussion of Never, it was pointed out that there are multiple different names that a bottom type might have and people might want to define their own. It seems that the "auto conformance to all protocols" behavior should apply to any fixed contents empty enum type, not just Never specifically.

It is probably worth solving this problem, but the motivation section of the proposal is not particularly strong. It doesn't explain specific use cases or give convincing examples of problems being solved, it talks about things abstractly.

I don't think this is an urgent problem, and would prefer to see a more global solution be investigated. If there is no better solution then perhaps this is a practical stopgap. I don't think that the rationale that a global solution "would require a lot more work to determine the design" (as stated in the alternatives considered section) is adequate justification to not do that work.

n/a

I read the proposal but did not follow any of the pitch threads.

-Chris

14 Likes

I'm with Matthew on the general concern: Never has no instances but it does have a type. That means that it does not automatically conform to protocols that have static requirements or initializer requirements or associated types. That makes the story more complicated than just "Never conforms to everything".

Equatable and Hashable seem like common currency protocols that are worth conforming to whether or not we're going to get a general solution. (I'd throw Comparable in there myself.)

Oh, and the last "alternative considered" doesn't work, because a type can only conform to a protocol in one way (conditionally or not). So that's an extra little bit of motivation to support this.

(The whole thing bends my brain a little, but thinking of concrete use cases like Result<Int, Never> being Equatable helps a lot.)

8 Likes

Thanks for mentioning this. I agree that use cases like this are exactly why this matters and why Equatable and Hashable (along with Comparable and Codable) are pretty important if we can get them in sooner than a more general solution which is complicated to get right.

Could Never as a type be a subtype of all types (including all existential types) without requiring it to conform to all protocols? Requiring conformance in order to be a subtype of the existential corresponding to a protocol is a design decision that makes sense in most cases but perhaps does not for a bottom type. I think the approach of loosening that requirement would let us sidestep the problems with static protocol requirements and would be consistent with bottom types in type theory as I understand them.

Never should be required to meet the semantic requirements of protocols it conforms to without needing to synthesize a trapping implementation that could be called. Requirements that require an instance be available prior to use (i.e. as an argument) would be exempt because we know they can never be invoked. All conformances of Never should provide a valid implementation of any static requirements that do not require an instance to be used_.

The way I see it, Never is a subtype of all types T, but that doesn't mean Never.Type is a subtype of T.Type. In fact, it is Never itself which is a subtype of T.Type. That's fine because type(of: someNeverValue) is impossible to execute, but it does lead to some funkiness.

1 Like

Never could also conform to the Error protocol.

4 Likes

That sounds fine to me, but it says nothing about protocol conformances (unless you are assuming that being a subtype of an existential is only possible when a conformance is present). Conformances are where the trouble creeps in.

If we're cherry-picking protocols to conform Never to, Error seems like another good one to whitelist, since it's useful to use Never to close off the error path in Result<T, Never>-like abstractions.

15 Likes

I think having Never conform to every protocol could cause issues, but it does seem arbitrary to only conform to a subset of protocols.

For example, if Never conforms to RangeReplaceableCollection then it gets init(). What would your init implementation be? I don’t think fatalError is a good answer as even the existence of the initialiser break type safety.

I think it’d be better if we could tweak the Hashable/Equatable auto-synthesis to ignore switch cases which are not constructible. That’s probably insufficient for the conditional conformance to work, but it will simplify the implementation a lot.

In the alternatives considered section you mention additional conformances being an unreasonable amount of work. I’m all for reducing that work, but how unreasonable actually is it? How often do you actually need to do this? It’d be great to get something more concrete on the impact of this proposal.

1 Like

Well, this seems pretty sensible to me, actually. First, because attempting to instantiate a Never is a logical error for which trapping is appropriate. Second, because there's a nice symmetry since we have func fatalError(/* ... */) -> Never: it seems entirely consistent that return Never() produces the same effect as result fatalError().

Is there a reason grounded in type theory that such behavior for a synthesized initializer would be unsound?

1 Like

Good point. Would it be reasonable to make it autoconform to any protocol that has no initializer requirement and no static method requirements?

-Chris

8 Likes

How about adding no conformances in the stdlib, but consider every non-static requirement fulfilled by Never?
That makes it extremely easy to add any conformance somebody might need.

From my real-life experience, nearly no developer actively uses Never, so extension Never: Equatable, Comparable, Hashable, Error {} wouldn't be a big burden.

1 Like

That sounds reasonable to me.

As an aside I think we have initialiser requirements in many more protocols than is necessary. The RangeReplaceableCollection init() has caused me a bunch of grief for collections that don't have sensible defaults. Perhaps this change will prompt us to split initialiser requirements into supplementary protocols, perhaps just a DefaultInitialised.

4 Likes