> On Dec 19, 2015, at 3:36 PM, Félix Cloutier <felix...@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.



> Félix
> 
>> Le 19 déc. 2015 à 14:28:44, Matthew Johnson <matt...@anandabits.com 
>> <mailto:matt...@anandabits.com>> a écrit :
>> 
>>> 
>>> On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>> 
>>> Sorry, I got a bit too excited and skimmed over the most important part of 
>>> the idea. So this is a special type of enum declaration in which you cannot 
>>> declare any new enum members. I personally have not seen a use for this in 
>>> my code but I would love to hear others' response to it. It is a very 
>>> interesting idea though.
>>> 
>>> I'm going to go out on a limb with an idea that is in the same vein as this 
>>> one: What if we favored composition over inheritance here, and made it so 
>>> that you could transparently refer to members of other enums *without* 
>>> having another enum as a backing type?
>>> 
>>> e.g., you have:
>>> enum NetworkException {
>>>   case NoInternetError, SecurityError
>>> }
>>> 
>>> enum ParseException {
>>>   case FailedResponse(statusCode: Int)
>>>   case EmptyResponse
>>>   case MissingField(fieldName: String)
>>> }
>>> 
>>> As two general classes of errors. But for a full API call wrapper, you 
>>> might want an error class that composes the two, so that when calling the 
>>> API call from your UI code, you can display a "please check your 
>>> connection" message for NoInternetError, a "Please log in" error for 
>>> FailedResponse with statusCode=401, or a "server error" message for any of 
>>> the rest. 
>>> 
>>> I wonder how do you and others feel about that use-case? I have certainly 
>>> seen it come up a lot in real-world projects that require resilient UI 
>>> interactions with nontrivial networking operations.
>>> 
>>> Here are some quick code samples off the top of my head for how we might go 
>>> about this (let's say the API operation is "change profile picture":
>>> 
>>> enum ChangePictureError {
>>>   include NetworkException
>>>   include ParseException
>>>   case PictureTooLarge
>>> }
>> 
>> By including all of the cases you make it possible for ChangePictureError to 
>> be a supertype of NetworkException and ParseException.  This is a pretty 
>> interesting idea.  It might be worth exploring.  
>> 
>> One thing that would need to be considered is that ideally if the actual 
>> values was a NetworkException case you would want to be able to call any 
>> methods exposed by Network Exception.  A good way to accomplish that might 
>> be to add implicit conversion as well as syntactic sugar for nested enums.  
>> So if we have this:
>> 
>> enum ChangePictureError {
>>   case NetworkException(NetworkException)
>>   case ParseException(ParseException)
>>   case PictureTooLarge
>> }
>> 
>> I can do this:
>> 
>> var error: ChangePictureError // set somewhere, can be set with a 
>> NetworkException or a PictureTooLarge
>> switch error {
>> case .NoNetworkError:                           // equivaluent to case 
>> .NetworkException(.NoNetworkError)
>> case .NoInternetError:                            // equivaluent to case 
>> .NetworkException(. NoInternetError)
>> case .FailedResponse(let statusCode): // equivaluent to case 
>> .ParseException(.FailedResponse(let statusCode))
>> case .EmptyResponse:                          // equivaluent to case 
>> .ParseException(.EmptyResponse)
>> case .MissingField(let fieldName):         // equivaluent to case 
>> .ParseException(. MissingField(let fieldName))
>> case .PictureTooLarge:
>> }
>> 
>> The syntactic sugar would only work for case names where there is no 
>> overlap.  Case names that overlap would need to be explicitly disambiguated. 
>>  The syntactic sugar and implicit conversions could allow for either 
>> single-level nesting or arbitrary nesting depth.  An example of arbitrary 
>> depth might be ParseException also containing a ValidationError case:
>> 
>> enum ValidationError {
>>   case OutOfRange
>>   case InvalidType
>> }
>> 
>> enum ParseException {
>>   case ValidationError(ValidationError)
>>   case FailedResponse(statusCode: Int)
>>   case EmptyResponse
>>   case MissingField(fieldName: String)
>> }
>> 
>> Mostly just thinking out loud here and exploring the idea.  What do others 
>> think of this?
>> 
>>> 
>>> or
>>> 
>>> enum ChangePictureError {
>>>   compose NetworkException.NoInternetError
>>>   compose ParseException.EmptyResponse
>>>   compose ParseException.FailedResponse(statusCode: Int)
>>>   case PictureTooLarge
>>> }
>>> 
>>> Not a proposal by any stretch of the imagination, just a potential 
>>> direction inspired by your idea, Felix.
>>> 
>>> 
>>> On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lyse...@gmail.com 
>>> <mailto:dennis.s.lyse...@gmail.com>> wrote:
>>> Felix, 
>>> 
>>> This seems to be very interestingly tied into your comments about 
>>> polymorphism in 'throws' type annotations. Would you not feel that allowing 
>>> enums to be built on top of other enums would promote the kind of egregious 
>>> proliferation of exception polymorphism that discourages so many from 
>>> following Java's checked exception model? 
>>> 
>>> On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org 
>>> <mailto:swift-evolution@swift.org>> wrote:
>>> Hi all,
>>> 
>>> Swift currently has more or less three conceptual types of enums: 
>>> discriminated unions, lists of unique tokens, and lists of value of a raw 
>>> type.
>>> 
>>> > // Discriminated unions
>>> > enum Foo {
>>> >       case Bar(Int)
>>> >       case Baz(String)
>>> > }
>>> >
>>> > // Lists of unique tokens (mixable with discriminated unions)
>>> > enum Foo {
>>> >       case Frob
>>> >       case Nicate
>>> > }
>>> >
>>> > // Lists of raw values
>>> > enum Foo: String {
>>> >       case Bar = "Bar"
>>> >       case Baz = "Baz"
>>> > }
>>> 
>>> I think that the last case could be made more interesting if you could use 
>>> more types as underlying types. For instance, it could probably be extended 
>>> to support another enum as the backing type. One possible use case would be 
>>> to have a big fat enum for all the possible errors that your 
>>> program/library can throw, but refine that list into a shorter enum for 
>>> functions that don't need it all.
>>> 
>>> > 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
>>> > }
>>> 
>>> This example could be made simpler if the `= .Foo` part was inferred from 
>>> the name, but you get the idea.
>>> 
>>> In this case, it would be helpful (but not required) that FileSystemError 
>>> was convertible into a MyLibError, so that it could be transparently 
>>> rethrown in a function that uses the larger enum. I personally don't see 
>>> why enums with a specified underlying type can't be implicitly converted to 
>>> it, but this is not currently the case and it probably deserves some 
>>> discussion as well.
>>> 
>>> Is there any interest in that?
>>> 
>>> Félix
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>  _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to