-1.
I think adding a Result type to the standard library could be nice, but in it's proposed form I'm going to have to vote no. The proposed Result<Value, Error>
is marked departure from the current error-handling system which uses the Error
protocol as first-class citizen. I think it would be a huge mistake to bifurcate Swift with two orthogonal error handling mechanisms.
I think the following amendments would make much more sense and be much easier for Swift developers to use today:
/// A value that represents either a success or failure, capturing associated
/// values in both cases.
@_frozen
- public enum Result<Value, Error> {
+ public enum Result<Value> {
/// A success, storing a `Value`.
case success(Value)
/// A failure, storing an `Error`.
case failure(Error)
/// The stored value of a successful `Result`. `nil` if the `Result` was a
/// failure.
public var value: Value? { get }
/// The stored value of a failure `Result`. `nil` if the `Result` was a
/// success.
public var error: Error? { get }
/// A Boolean value indicating whether the `Result` as a success.
public var isSuccess: Bool { get }
/// Evaluates the given transform closure when this `Result` instance is
/// `.success`, passing the value as a parameter.
///
/// Use the `map` method with a closure that returns a non-`Result` value.
///
/// - Parameter transform: A closure that takes the successful value of the
/// instance.
/// - Returns: A new `Result` instance with the result of the transform, if
/// it was applied.
public func map<NewValue>(
- _ transform: (Value) -> NewValue
- ) -> Result<NewValue, Error>
// closures that throw should be automatically create new results with thrown errors
+ _ transform: (Value) throws -> NewValue
+ ) -> Result<NewValue>
/// Evaluates the given transform closure when this `Result` instance is
/// `.failure`, passing the error as a parameter.
///
/// Use the `mapError` method with a closure that returns a non-`Result`
/// value.
///
/// - Parameter transform: A closure that takes the failure value of the
/// instance.
/// - Returns: A new `Result` instance with the result of the transform, if
/// it was applied.
- public func mapError<NewError>(
- _ transform: (Error) -> NewError
- ) -> Result<Value, NewError>
// This would be vastly simplified, simply take an error and return a new error if you so choose
+ public func mapError(
+ _ transform: (Error) throws -> Error
+ ) -> Result<Value>
/// Evaluates the given transform closure when this `Result` instance is
/// `.success`, passing the value as a parameter and flattening the result.
///
/// - Parameter transform: A closure that takes the successful value of the
/// instance.
/// - Returns: A new `Result` instance, either from the transform or from
/// the previous error value.
public func flatMap<NewValue>(
- _ transform: (Value) -> Result<NewValue, Error>
- ) -> Result<NewValue, Error>
// Again throwing would be permitted integrating nicely with our existing system
+ _ transform: (Value) throws -> Result<NewValue>
+ ) -> Result<NewValue>
/// Evaluates the given transform closure when this `Result` instance is
/// `.failure`, passing the error as a parameter and flattening the result.
///
/// - Parameter transform: A closure that takes the error value of the
/// instance.
/// - Returns: A new `Result` instance, either from the transform or from
/// the previous success value.
- public func flatMapError<NewError>(
- _ transform: (Error) -> Result<Value, NewError>
- ) -> Result<Value, NewError>
// Again a vastly simplified signature with handling of existing niceties
+ public func flatMapError(
+ _ transform: (Error) throws -> Result<Value>
+ ) -> Result<Value>
/// Evaluates the given transform closures to create a single output value.
///
/// - Parameters:
/// - onSuccess: A closure that transforms the success value.
/// - onFailure: A closure that transforms the error value.
/// - Returns: A single `Output` value.
public func fold<Output>(
onSuccess: (Value) -> Output,
onFailure: (Error) -> Output
) -> Output
}
- extension Result where Error: Swift.Error {
// Simplifies these extensions
+ extension Result {
/// Unwraps the `Result` into a throwing expression.
///
/// - Returns: The success value, if the instance is a success.
/// - Throws: The error value, if the instance is a failure.
public func unwrapped() throws -> Value
}
- extension Result where Error == Swift.Error {
+ extension Result {
/// Create an instance by capturing the output of a throwing closure.
///
/// - Parameter throwing: A throwing closure to evaluate.
@_transparent
public init(_ throwing: () throws -> Value)
- /// Unwraps the `Result` into a throwing expression.
- ///
- /// - Returns: The success value, if the instance is a success.
- /// - Throws: The error value, if the instance is a failure.
- public func unwrapped() throws -> Value
- /// Evaluates the given transform closure when this `Result` instance is
- /// `.success`, passing the value as a parameter and flattening the result.
- ///
- /// - Parameter transform: A closure that takes the successful value of the
- /// instance.
- /// - Returns: A new `Result` instance, either from the transform or from
- /// the previous error value.
- public func flatMap<NewValue>(
- _ transform: (Value) throws -> NewValue
- ) -> Result<NewValue, Error>
}
extension Result : Equatable where Value : Equatable, Error : Equatable { }
extension Result : Hashable where Value : Hashable, Error : Hashable { }
extension Result : CustomDebugStringConvertible { }
What changes would need to be made to Swift to have an Error
generic parameter make sense? There are a few things that could help:
-
Default generic types
If you can declare Result<Value, Error = Swift.Error>
that would dramatically reduce the boilerplate for what will be, by far, the most common use case.
-
Add typed throws
Now a Result
specific Error
type makes total sense. Without typed throws it would be a strong divergence from error handling in modern Swift.
-
Add union types
The biggest pain in practice with typed errors would be how unwieldy type composition is. To compose errors that bubble up would require defining enum
s and wrapper types everywhere. If you could simply declare that a function throws MyError | BespokeLibraryError
then the usability cost of typed errors is dramatically reduced.
EDIT: Which I should note will not happen, proposals for union types have been rejected. This is a reason to be skeptical that typed throws will ever be adopted.
Let me emphasize that I love types and I love Result
, which I've used in my own work. But defining a Result
specific Error
type doesn't make sense for Swift, not without some significant changes to the language. That's why I'm against this proposal.