> On Jul 5, 2016, at 5:54 PM, Ben Rimmington <m...@benrimmington.com> wrote:
> 
>> 
>> On 6 Jul 2016, at 01:02, Douglas Gregor <dgre...@apple.com 
>> <mailto:dgre...@apple.com>> wrote:
>> 
>>> On Jul 5, 2016, at 5:00 PM, Ben Rimmington <m...@benrimmington.com 
>>> <mailto:m...@benrimmington.com>> wrote:
>>> 
>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md
>>>  
>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md>>
>>> 
>>> The new protocols could be combined into a single CustomNSError protocol.
>>> This would mirror the NSError class APIs, which are being customized.
>> 
>> Why is that good? The two primary protocols—LocalizedError and 
>> RecoverableError—provide a more focused, easy-to-understand experience for 
>> opting in to specific behavior. CustomNSError is a fallback for “I want to 
>> do something special with the generated NSError”.
> 
> You wrote previously:
> "To a close approximation, there are no use sites of these protocols."
> <http://thread.gmane.org/gmane.comp.lang.swift.evolution/22485/focus=22919 
> <http://thread.gmane.org/gmane.comp.lang.swift.evolution/22485/focus=22919>>
> 
> If the Printable protocol was renamed to CustomStringConvertible to 
> discourage use sites, then having a single CustomNSError protocol should have 
> a similar effect.

We don’t need to discourage use sites; I just don’t expect them to be common 
because it’s the library that will be querying these conformances. Regardless, 
we shouldn’t contort a protocol design to discourage usage; we should make it 
clear what conforming to the protocol implies.

> The protocol will be as easy-to-understand as the NSError class itself. If 
> there are default implementations for all requirements, a conforming type can 
> still opt into specific behavior.

“As easy-to-understand as the NSError class” is not the goal here. We want to 
do better, and that means detangling the various different things that NSError 
puts together.

With this proposal, you define an error type like this:

enum HomeworkError : ErrorProtocol {
  case forgotten
  case lost
  case dogAteIt
}

To give it a nice, localized error message (or other localized information), 
you conform to LocalizedError:

extension HomeworkError : LocalizedError {
  var errorDescription: String? {
    switch self {
    case .forgotten: return NSLocalizedString("I forgot it")
    case .lost: return NSLocalizedString("I lost it")
    case .dogAteIt: return NSLocalizedString("The dog ate it")
    }
  }
}

and if you want to implement recovery attempts for your error, you conform to 
RecoverableError:

extension HomeworkError : RecoverableError {
  // implementation here
}

You can catch these errors with “catch let error as HomeworkError”, or “catch 
HomeworkError.forgotten”, or whatever.

Nowhere in any of that did I mention NSError, or error domains, or codes, or 
user-info dictionaries. You shouldn’t need them with this model, at all. 
NSError is bridged away completely, and is an implementation detail of 
Objective-C interoperability. CustomNSError, and references to the NSError 
type, is an escape hatch so that one can get precise control over the 
interoperability with NSError for those (hopefully rare) cases where the Swift 
error-handling model can’t express something that NSError can.


> 
>>> Instead of using NSError.setUserInfoValueProvider(forDomain:provider:) 
>>> could you wrap the CustomNSError value inside an NSError subclass?
>>> 
>>>     class _CustomNSError: NSError {
>>> 
>>>         private let _error: CustomNSError
>>> 
>>>         init(_error: CustomNSError) {
>>>             self._error = _error
>>>             super.init(
>>>                 domain:   _error.dynamicType.errorDomain,
>>>                 code:     _error.errorCode,
>>>                 userInfo: _error.errorUserInfo)
>>>         }
>>> 
>>>         override var localizedDescription: String {
>>>             return _error.errorDescription ?? super.localizedDescription
>>>         }
>>> 
>>>         override var localizedFailureReason: String? {
>>>             return _error.failureReason ?? super.localizedFailureReason
>>>         }
>>> 
>>>         override var localizedRecoverySuggestion: String? {
>>>             return _error.recoverySuggestion ?? 
>>> super.localizedRecoverySuggestion
>>>         }
>>> 
>>>         override var localizedRecoveryOptions: [String]? {
>>>             return _error.recoveryOptions ?? super.localizedRecoveryOptions
>>>         }
>>> 
>>>         override var recoveryAttempter: AnyObject? {
>>>             if _error.recoveryOptions != nil {
>>>                 return _NSErrorRecoveryAttempter(error: _error)
>>>             } else {
>>>                 return super.recoveryAttempter
>>>             }
>>>         }
>>> 
>>>         override var helpAnchor: String? {
>>>             return _error.helpAnchor ?? super.helpAnchor
>>>         }
>>>     }
>> 
>> We could, but why? This is precisely what user-info value providers were 
>> designed for.
> 
> If the NSError.setUserInfoValueProvider(forDomain:provider:) method isn't 
> available in all supported target platforms, an NSError subclass seems to be 
> the simpler option.

When/where it is available, 
NSError.setUserInfoValueProvider(forDomain:provider:) is the solution 
recommended by Cocoa as the best way to lazily populate the userInfo 
dictionary, so it makes sense for us to use that mechanism. Sure, we need to 
implement a fallback, but the use of that fallback will go away at some point 
and we’ll be back to one implementation.

        - Doug


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

Reply via email to