SE-0215 - Conform Never to Equatable and Hashable

Not sure it is really worth the trouble.

What is your evaluation of the proposal?
+1

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

Does this proposal fit well with the feel and direction of Swift?
I'm not completely sure. Conformance to just these protocols seems fairly reasonable as a minimum but not terribly principled. Short of making never an inhabitant of every type (protocols included), I think that it will always feel arbitrary. Acceptable, but arbitrary.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those? Never in Haskell is useful but it is an inhabitant of every type.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I've followed the various discussions

1 Like

Questions about future plans making Never the bottom type:

Judging by the history, AnyObject was a magical protocol which created a lot of issues has now become a keyword. In That sense Never is an empty enum for now as a reserved type.

  • If we're ever going to make it the bottom type (which I personally would love to see), any chance that we would need to abandon the enum then and make it a keyword like Any, AnyObject etc.?
  • If so, would such extensions like proposed be troublesome to remove from the language in the future?
1 Like

Is it an instance of every typeclass? That would be the equivalent of conforming to every protocol.

1 Like

Never can be substituted for any generic type A because you can't create instances of it - so you will be forced to deal with optionals to account for values not being occupied. But Never.Type cannot be substituted for any metatype A.Type, because you do have an "instance" of it - Never.self.

If you allow substituting Never for any generic type parameter regardless of the constraints on that type then you will have cases where static, initializer and associated type requirements are used by that generic code. in some cases such as == we still have a static proof that the member cannot be used (because using it would require an existing instance of Never). However, that will often not be the case. Deciding how to handle this is one of the most important design decisions involved in making Never a true bottom type IMO.

yeah, what I mean is that those all come from the metatype. Once you, say, look up an associated type, you're performing operations meant for an A.Type on Never.Type (which, unlike instances of Never, is a real thing). It's different than just marking a value of type A as uninhabitable.

Gotcha, yeah. I think a good way to summarize the design problem is that in order to be a true bottom type Never must be a subtype of all types including existentials of protocols which place requirements on the metatype. The options appear to be:

  1. Make an exception for Never in the subtyping rules for existentials.
  2. Synthesize trapping implementations on Never for all static and initializer requirements of all protocols as well as a type alias equal to Never for all associated type requirements on all protocols.
  3. Don't make Never a true bottom type.

#1 seems like the least bad option to me right now but there could well be edge cases I haven't thought of. #2 is obviously what I am arguing against. :slight_smile:

It is but I am pretty sure that type classes are a bit more than protocols.

Really? What happens when generic code uses mempty?

A runtime exception.

A few more questions about the future of Never:

  • If Never is a subtype of everything it will technically inherit every possible API?

  • protocol A {
      static var foo: Int { get }
    }
    
    protocol B {
      static var foo: Bool { get }
    }
    
    Never.foo // how would this collision be resolved?
    
  • How would autocompletion be affected on Never? If we type Never. in Xcode it will show all public API including colliding API?

  • Shouldn't Never become a keyword like AnyObject so that no one would be able to extend it on it's own?

  • Were these issues considered in regard of extending Never now?

No, I'm not well versed in type theory at all, hence the question to the community. However, I am of the understanding that this poses some problems for internal consistency of Swift:

  • In Swift, if C1 is a subtype of C0, then C1 inherits the protocol conformances of C0.
  • If Never is a true bottom type, it is a subtype of all types.
  • Therefore, if a protocol P has at least one non-uninhabited conforming type T, then it follows that (in Swift) Never, being a subtype of T, conforms to P. I suppose Never doesn't need to conform to a protocol P0 with no other conforming types, but I'm not certain how that would interact with private types, for example.

To be clear, my concern is not about Never being the subtype of the protocol existential (which indeed doesn't even conform to the protocol), but about its being a subtype of conforming types, which everywhere in Swift have available the extension methods and non-overridden default implementations of protocols to which they conform.

I don't see a reason why the compiler shouldn't reject any actual attempted initialization of Never at compile time, but if Never doesn't conform to protocols with initializer requirements at all, you're prohibiting useful generic uses that have nothing to do with the initializer or the protocol existential. In other words, I don't think it's a binary choice between avoiding "landmines" and having a true bottom type that follows the same rules as all other subtyping relationships in the language.

1 Like

Really? I looked into this and I don't see an instance of Data.Monoid for Data.Void listed here: Data.Void or here Data.Monoid. What am I missing? Where is the instance declared?

Ahh, I see what you're getting at. I think the point @beccadax made upthread is relevant here:

This interpretation implies that Never would have all instance members but not all static members. Must a subtype relationship of T: U always imply a subtype relationship T.Type: U.Type? The distinction is subtle but extremely important. I don't believe the notion of "bottom type" requires this to be the case. However, if this is currently always the case then you are right that breaking that relationship for Never would be an inconsistency with the rest of the language. It may still be better than the alternatives.

I'm not suggesting that the compiler should reject all attempts or that Never shouldn't be prohibited from conforming to protocols with initializer requirements. But I am arguing that the compiler and standard library should not provide any trapping initializers for Never. I am also arguing the language and standard library should not provide conformances to protocols with members that may actually be used except where those conformances have received direct human consideration and the conformance is understood to correctly meet the semantic requirements of the protocol.

1 Like

Using an extension of @Chris_Lattner3 's proposal for dynamicCallable SE-0216: User-defined dynamically callable types - #68 by dan-zheng (current proposal doesn't handles static or init), you could make Never conform to all types. For all static and init functions it dispatches to the dynamicallyCallStaticMethod method that throws a fatal error. EG:

@dynamicCallable
struct Never {
  // Catches all `static` and `init` calls.
  @discardableResult
  func dynamicallyCallStaticMethod(withName: String, withArguments: [Any]) -> Never {
    fatalError("`static` function or `init` called on `Never`!")
  }
}

From nothing, comes everything.

Very good proposal. I

Excited but hope we can use "Never" to uplift Swift Math.

Though I do propose changing "Never" to "Nothing" and making everything conform to "Nothing". Sounds more intuitive as unintuitive as that sounds. :slight_smile:

You might be interested in reading the proposal that introduced Never and why the name was chosen swift-evolution/0102-noreturn-bottom-type.md at master · apple/swift-evolution · GitHub

1 Like

Proposal Accepted

Thank you to everyone who participated in the review of SE-0215. The review discussion was very insightful both on considering the specific problems being addressed by this proposal as well perspective on the role and evolution of the Never type.

The Core Team discussed the review, and reached the following conclusions:

  • The proposal should be accepted with the new explicit conformances to Equatable and Hashable be added to Never. This addresses a real point of friction users are experiencing with the use of Never.

  • For the same reasons conformances to Hashable and Equatable are being added to Never, the Core Team felt that conformances to Error and Comparable should also be added to Never as part of accepting this proposal. Both of these additional protocol conformances were brought up during the review.

  • Never should become a blessed bottom type in the language. This matches with semantics in other languages and its intended role in Swift.

With respect to the latter, the Core Team discussed what Never being a bottom type actually meant. From that discussion we reached the following conclusions:

  • Semantically, as a bottom type, it would mean that Never should be implicitly convertible to any other type. This composes well in the type system because of the fact that instances of Never cannot be instantiated.

  • However, being a bottom type does not imply that Never should implicitly conform to all protocols. Instead, convenient protocol conformances for Never should be added as deemed useful or necessary.

There are various details to suss out with making Never a bottom type, and thus making it a bottom type would be served by a separate and well-considered proposal. It is the opinion of the Core Team that making Never a bottom type would be separate from it conforming to all protocols, and thus the motivation of this particular proposal and the particular problems it addresses stands on its own.

With respect to protocol conformances, the Core Team felt the language has clearly moved in a direction where explicit protocol conformance is fundamental to the language model. The compiler has grown affordances to make the implementation of protocol conformances easy in many cases (e.g., synthesized implementations of Hashable) but that the explicit protocol conformance is quite important. Adding rules for implicit protocol conformances — something that has been considered in Swift’s history — ends up adding real complexity to the language and its implementation that can be hard to reason about by a user as well as by the language implementation.

Thank you again for everyone who participated in this review!

15 Likes