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.
Félix
Le 19 déc. 2015 à 14:28:44, Matthew Johnson <matthew@anandabits.com> a
écrit :
On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution < > 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.lysenko@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> 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
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution