[swift-evolution] [Pitch] Use enums as enum underlying types

Félix Cloutier felixcca at yahoo.ca
Mon Dec 21 19:53:14 CST 2015


I agree that there's something about the inheritance that's inside out, since the enum restricts the domain of the underlying type instead of expanding it. However, it is consistent with the current enum semantics when they're based off raw values.

It seems to me that the relationships otherwise make sense: looking at the declaration syntax `enum FileSystemError: MyLibError`, there's no surprise that any FileSystemError can be converted to a MyLibError.

The fact that enums are value types and that value types can't usually use inheritance does mean that there will be no vtable involved, however, and that could go against the principle of least surprise. This is less of an issue with composition since fields would be expected to have their own methods.

I like the prospect of automatically synthesizing a sub-enum for cases that are actually thrown as this would make catching much shorter. In that regard, sub-enums stand out compared to inclusive enums when you have two functions that can throw overlapping subsets of errors:

> enum ChangePictureError {
> 	case NoInternet, Security
> 	case PictureTooBig
> }
> 
> func doThis() throws {
> 	guard checkInternet() else {
> 		throw ChangePictureError.NoInternet
> 	}
> 	guard checkAllowed() else {
> 		throw ChangePictureError.Security
> 	}
> }
> 
> func doThat() throws {
> 	guard checkInternet() else {
> 		throw ChangePictureError.NoInternet
> 	}
> 	guard checkPictureSize() else {
> 		throw ChangePictureError.PictureTooBig
> 	}
> }

With composed enums, you'd have NetworkError, and ChangePictureError that includes NetworkError and provides an additional PictureTooBig. However, with both functions, there's at least one case that can't be thrown. Even with annotated throws, this means you still need irrelevant case(s) to prove exhaustiveness if you can't synthesize sub-enums and don't want to complicate function metadata to include every possibly thrown case. If the compiler is allowed to create sub-enums, you won't need a catch-all to prove exhaustiveness.

Inclusive enums and sub-enums aren't necessarily mutually exclusive, either.

Félix

> Le 20 déc. 2015 à 20:13:14, Matthew Johnson <matthew at anandabits.com> a écrit :
> 
>> 
>> On Dec 19, 2015, at 3:36 PM, Félix Cloutier <felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>> wrote:
>> 
>> I'm biased as the pitcher, but I find that an "inheritance" model would be more straightforward than an inclusive model.
>> 
>> If I understand you correctly, with this model:
>> 
>>> enum NetworkException {
>>>   case NoInternetError, SecurityError
>>> }
>>> 
>>> enum ChangePictureError {
>>>   case NetworkException(NetworkException)
>>>   case ParseException(ParseException)
>>>   case PictureTooLarge
>>> }
>> 
>> you're saying that we should be able to write:
>> 
>>> let x: ChangePictureError = NetworkException.NoInternetError
>> 
>> The implicit conversion from NetworkException to ChangePictureError reminds me of C++ implicit constructors, which are generally frowned upon, so I'm not sure that this is the best way forward.
>> 
>> On the other hand, going back to my original example:
>> 
>>> enum MyLibError: ErrorType {
>>> 	case FileNotFound
>>> 	case UnexpectedEOF
>>> 	case PermissionDenied
>>> 	// ... 300 cases later
>>> 	case FluxCapacitorFailure
>>> 	case SplineReticulationError
>>> }
>>> 
>>> enum FileSystemError: MyLibError {
>>> 	case FileNotFound = .FileNotFound
>>> 	case UnexpectedEOF = .UnexpectedEOF
>>> 	case PermissionDenied = .PermissionDenied
>>> }
>> 
>> 
>> I can easily rationalize that FileSystemError is implicitly convertible to MyLibError because of the "inheritance" relationship.
>> 
> 
> As I said, I was mostly thinking out loud about possibilities here.  
> 
> The supertype / subtype relationship makes sense to me but an inheritance relationship does not.  It doesn’t make sense because enums are value types and also because it gets the supertype / subtype relationship backwards.  Just because you have a FileSystemError you do not necessarily have a MyLibError.  However, if you have a MyLibError you *do* have something that can be a FileSystemError (whether by include or by composition or whatever mechanism we might use).
> 
> I agree that implicit conversions are generally a bad thing and I am not necessarily convinced that the idea I outlined is a good one. However, it does follow the pattern of allowing implicit conversion for subtype / supertype relationships *if* we consider the nested enum case to effectively make ChangePictureError a supertype of NetworkExceptionError.  In other words, all NetworkExceptions *can be* a ChangePictureError.  
> 
> Swift already includes a number of implicit conversions for subtype / supertype relationships: reference type inheritance, protocol conformance, values to optional values, etc.  Chris has talked about possibly extending this further.  This is the line of thinking that lead to my writeup.  Whether it makes sense to extend it in the way I outlined or not I am not sure.  But that makes more sense than the other ideas I have seen in this thread so far.
> 
> The problem I see with the include idea is that it doesn’t consider the type holistically, it only considers the cases.  What about other initializers and methods?  The initializers are probably ok because they would only reference cases that were included, but the methods would not be ok as they would not match the additional cases in the containing enum.  
> 
> Including the cases but losing access to the methods seems like a worse solution than what we have today with nested enums.  I don’t think there is necessarily a good solution that isn’t a nested enum.  That is why I started thinking about ways to make them more convenient by taking advantage of the subtype / supertype relationship that is already effectively latent in nested enums.  I’m not sure it is a good idea, but I don’t see any better path to improve on current state.
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151221/85d79eec/attachment.html>


More information about the swift-evolution mailing list