ellie20
(ellie20)
1
The equivalent methods on other types, like Array and Optional, rethrow (propagate errors from their closure argument). Given that Result isn't supposed to be used as a replacement for throwing, I don't see a reason for why Result's methods shouldn't rethrow.
Jon_Shier
(Jon Shier)
2
This was discussed in the original proposal(s) but I can’t recall the reasoning why they aren’t. I think it’s to avoid weirdness like try Result {}.map {}, as it seems like such a thing should just be captured into the Result value itself. Unfortunately Result’s API surface has never been expanded to make those operations nicer to do.
rethrows is deprecated for most use cases, including this one.
- Unfortunately,
Result.map is named wrong. Its correct name is mapSuccess, and as you suggested, it, and what should be named map, should be able to throw.
extension Result {
func mapSuccess<NewSuccess, Error>(
_ transform: (Success) throws(Error) -> NewSuccess
) throws(Error) -> Result<NewSuccess, Failure> {
switch self {
case .success(let success): .success(try transform(success))
case .failure(let failure): .failure(failure)
}
}
typealias Get = () throws(Failure) -> Success
func map<NewSuccess, NewFailure, Error>(
_ transform: (Get) throws(Error) -> Result<NewSuccess, NewFailure>
) throws(Error) -> Result<NewSuccess, NewFailure> {
try transform(get)
}
func flatMap<NewSuccess, NewFailure>(
_ transform: (Get) throws(NewFailure) -> NewSuccess
) -> Result<NewSuccess, NewFailure> {
.init { () throws(_) in try transform(get) }
}
}
jrose
(Jordan Rose)
4
map is named wrong
It is consistent with Result in Rust, Elm, Kotlin, and Ocaml. Result is a success-biased type.
7 Likes
I doubt that biasing ever makes sense when multiple types are involved. It leads to problems like we're seeing here, better demonstrated by Either's map and mapLeft. 
What Result is missing is a full conversion function, either transforming a Result, or something functionally equivalent but more versatile, like the type of Result.get, as I showed above. As it is, the flatMaps allow transformations from successes to errors, and vice versa, within the same type, but not the much more useful transformations between two success types, and two error types, simultaneously. Or even just switching the error type based on the success value.
This is handleable via map and flatMap, because Result is just a cached version of its get signature.
enum Failure: Swift.Error { case notGood }
let boolResult = Result<_, Failure>.success(false)
let stringResult = "This is the mapped result."
let doesThrow = try boolResult.mapSuccess { success throws(Failure) in
if success { stringResult } else { throw .notGood }
}
var doesntThrow = boolResult.mapSuccess { success in stringResult }
doesntThrow = boolResult.flatMapSuccess { success throws(Failure) in
if success { stringResult } else { throw .notGood }
}
extension Result {
func flatMapSuccess<NewSuccess>(
_ transform: (Success) throws(Failure) -> NewSuccess
) -> Result<NewSuccess, Failure> {
.init { () throws(_) in try transform(get()) }
}
}