Sorry, got a little excited in answering and just re-read the proposal. Ignore the part of "extra cruft", since I'll never have to see it unless I want to override it. I thought that the static dispatch bit later on in the proposal implies that that wouldn't work so well, but I might have misread.
On Sun, Dec 20, 2015 at 12:00 AM Dennis Lysenko <[email protected]> wrote: > Charles, > > While I agree it's unfortunate that there isn't much interop between > ErrorType and NSError, the decision from the core team to make ErrorType an > empty protocol seems to have been a premeditated one. I would love to have > someone on the core team respond here in my stead, but I'll try and present > my side of it. > > I have a lot of errors that come up internally in my app's workings. I > subscribe to Swift's error handling rationale regarding recoverable errors. > For example, my internal networking library has a NoInternetError which > never needs to be presented using built-in cocoa error presentation, but > instead has a different presentation scheme for each different context > (e.g. if it happened while loading a tableview, it shows up as the > placeholder text. if it happened in response to a user action, it displays > an alert.) I, therefore, strongly disagree with adding any extra members to > the ErrorType protocol: it's cruft that will never be of use to me. > > Now, this is the part that I'm not sure about and I might be corrected by > someone from the core team: It seems that part of the rationale for > introducing the general ErrorType is to move *away* from NSError. NSError > is antiquated and carries information that is often not even used (take a > survey of 100 swift programmers, and maybe 40 of them will care to tell you > what an error domain is). > > A lot of this probably hinges on typed `throws` annotations as well; if we > get those, then you never have to catch general ErrorType unless you wrote > a function to throw general ErrorType, so you could make all your error > types conform to a custom protocol with all this NSError-conformant info in > it without worrying about static dispatch. > > On Sat, Dec 19, 2015 at 10:50 PM Charles Srstka via swift-evolution < > [email protected]> wrote: > >> This is a bit of a pre-proposal; I wanted to bounce some ideas off the >> community before writing up a formal proposal, to see how people felt about >> this. >> >> The Problem: >> >> Swift introduces the very nifty ErrorType protocol, which, if implemented >> as an enum, allows one to associate arguments with the specific error cases >> with which they are relevant, as in this example: >> >> enum MyError: ErrorType { >> case JustFouledUp >> case CouldntDealWithFile(url: NSURL) >> case CouldntDealWithSomeValue(value: Int) >> } >> >> This is great, because it ensures that the file-related error will always >> have a URL object, whereas it won’t be included in the cases where it is >> irrelevant. >> >> Unfortunately, the Cocoa APIs needed to display an error object to the >> user all take NSErrors, and the conversion from other ErrorTypes to >> NSErrors is not very good; using “as NSError” to convert a MyError will >> result in something that has all of the associated values removed, and the >> message displayed to the user will be a cryptic and not particularly >> user-friendly “MyError error 1” rather than something more descriptive. One >> can define an “toNSError()” method on one’s own error type that will >> properly propagate the NSError’s userInfo dictionary such that it will be >> displayed in a more meaningful way: >> >> enum MyError: ErrorType { >> case JustFouledUp >> case CouldntDealWithFile(url: NSURL) >> case CouldntDealWithSomeValue(value: Int) >> >> func toNSError() -> NSError { >> var userInfo = [String : AnyObject]() >> let code: Int >> >> >> switch self { >> case .JustFouledUp: >> userInfo[NSLocalizedFailureReasonErrorKey] = "Something >> fouled up!" >> code = 0 >> case let .CouldntDealWithFile(url): >> userInfo[NSLocalizedFailureReasonErrorKey] = "Something went >> wrong with the file \(url.lastPathComponent ?? "(null)")." >> userInfo[NSURLErrorKey] = url >> code = 1 >> case let .CouldntDealWithSomeValue(value): >> userInfo[NSLocalizedFailureReasonErrorKey] = "This value >> isn't legit for some reason: \(value)" >> code = 2 >> } >> >> >> return NSError(domain: "MyError", code: code, userInfo: userInfo) >> } >> } >> >> However, since this method will only be invoked if called explicitly, one >> has to make sure to include .toNSError() every time when throwing an error >> object, or else it will not display properly; one can never just throw a >> MyError object. This is error-prone (no pun intended), and also prevents >> things like making the error conform to Equatable and comparing it against >> an ErrorType we received from some other method. >> >> I propose an addition to the ErrorType protocol, provisionally entitled >> “toNSError()”, but which another name could be given if something else is >> determined to be more appropriate. This method would be called by the >> system whenever “as NSError” is called on something that implements >> ErrorType. While this method would be added to the protocol, a default >> implementation would be provided in an extension, so that implementers of >> ErrorType would never actually need to override it unless very specific >> customization was required. For this default implementation, several other >> properties corresponding to some of the more commonly-used NSError keys >> (defined with default implementations returning nil) would be defined and >> referenced, and anything that returned non-nil would be packaged into a >> userInfo dictionary, like so: >> >> protocol ErrorType { >> var description: String? { get } >> var failureReason: String? { get } >> var recoverySuggestion: String? { get } >> var recoveryOptions: [String]? { get } >> var recoveryAttempter: AnyObject? { get } >> var helpAnchor: String? { get } >> var underlyingError: ErrorType? { get } >> var URL: NSURL? { get } >> >> >> func toNSError() -> NSError >> } >> >> extension ErrorType { >> var description: String? { return nil } >> var failureReason: String? { return nil } >> var recoverySuggestion: String? { return nil } >> var recoveryOptions: [String]? { return nil } >> var recoveryAttempter: AnyObject? { return nil } >> var helpAnchor: String? { return nil } >> var underlyingError: ErrorType? { return nil } >> var URL: NSURL? { return nil } >> >> >> func toNSError() -> NSError { >> let domain = // do what “as NSError” currently does to generate >> the domain >> let code = // do what “as NSError” currently does to generate >> the code >> >> >> var userInfo = [String : AnyObject]() >> >> >> if let description = self.description { >> userInfo[NSLocalizedDescriptionKey] = description >> } >> >> if let failureReason = self.failureReason { >> userInfo[NSLocalizedFailureReasonErrorKey] = failureReason >> } >> >> >> if let suggestion = self.recoverySuggestion { >> userInfo[NSLocalizedRecoverySuggestionErrorKey] = suggestion >> } >> >> >> if let options = self.recoveryOptions { >> userInfo[NSLocalizedRecoveryOptionsErrorKey] = options >> } >> >> >> if let attempter = self.recoveryAttempter { >> userInfo[NSRecoveryAttempterErrorKey] = attempter >> } >> >> if let anchor = self.helpAnchor { >> userInfo[NSHelpAnchorErrorKey] = anchor >> } >> >> >> if let underlying = self.underlyingError { >> userInfo[NSUnderlyingErrorKey] = underlying.toNSError() >> } >> >> >> if let url = self.URL { >> userInfo[NSURLErrorKey] = url >> >> >> if url.fileURL, let path = url.path { >> userInfo[NSFilePathErrorKey] = path >> } >> } >> >> >> return NSError(domain: domain, code: code, userInfo: userInfo) >> } >> } >> >> Thanks to all the default implementations, the error type would only have >> to implement the properties corresponding to the userInfo keys that the >> implementer deems relevant, as in: >> >> enum MyError: ErrorType { >> case JustFouledUp >> case CouldntDealWithFile(url: NSURL) >> case CouldntDealWithSomeValue(value: Int) >> >> >> var failureReason: String? { >> switch self { >> case .JustFouledUp: >> return "Something fouled up!" >> case let .CouldntDealWithFile(url): >> return "Something went wrong with the file \(url. >> lastPathComponent ?? "(null)")." >> case let .CouldntDealWithSomeValue(value): >> return "This value isn't legit for some reason: \(value)" >> } >> } >> >> >> var URL: NSURL? { >> switch self { >> case let .CouldntDealWithFile(url): >> return url >> default: >> return nil >> } >> } >> } >> >> This could then be created and passed to an API taking an NSError like so: >> >> let err = MyError.CouldntDealWithFile(url: NSURL(fileURLWithPath: >> "/path/to/file")) >> >> NSApp.presentError(err as NSError) >> >> and everything would be properly presented to the user. >> >> Similar functionality could be added to the protocol for conversions in >> the other direction, although this would be more difficult and would >> require more work on the implementer’s part. >> >> The biggest problem I see to the idea is the use of references to >> Foundation types—NSError and NSURL—in the ErrorType definition, which may >> be undesired in a pure-Swift environment. In particular, usage of the NSURL >> type for the ‘URL’ property, which could have useful applications outside >> of simple Obj-C interop, could be irksome. Normally I would just propose >> adding things in an extension, but of course in this case, declaring >> methods in protocol extensions causes them to be statically dispatched, >> which could result in the wrong methods being called if the caller thought >> it was looking at a generic ErrorType rather than the specific concrete >> type. Perhaps this could spark a new discussion on whether there ought to >> be a way to declare dynamically-dispatched methods in protocol extensions. >> It’s also possible that Swift could use a built-in URL type, equivalent to >> Foundation’s NSURL, eliminating the need for any NS types other than >> NSError here. It’s also possible that since there appears to be an >> open-source implementation of Foundation in the github repository, that >> this isn’t even an issue and is something we can just leave in. At any >> rate, I thought this might be an interesting starting point for discussion. >> >> Of course, an alternative solution could be to define “domain”, “code”, >> and “userInfo” properties (I know the first two are already in there, but >> this would make them public) in the protocol and just use those. These >> could also get default implementations that would work similarly to what is >> above, which, if Swift gained a native URL type, could completely eliminate >> Foundation types from the public interface. >> >> What do you think? >> >> Charles >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] >> https://lists.swift.org/mailman/listinfo/swift-evolution >> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
