This isn't just a question of interoperability. I cannot imagine working on a project that doesn't use a Result
type. When a foundational type is that pervasive it really does belong in the standard library. The only reason it isn't there yet is that we haven't settled on a design. This thread feels like it is heading in the right direction and I really hope it leads to an accepted proposal.
This may not match the experience of all Swift programmers, but it closely matches mine. I very often need a Result type. And if not on API boundaries, at least in implementations. I mean that even if there are ways to write an API which does not expose a Result type, this type is very often useful anyway.
Sure. It's the same two basic ideas that inform most of the proposal. First, that's the common spelling of those methods from most of the community implementations, so users will be familiar with it. Second, It's also the spelling users will be familiar with from other types in the standard library, like Optional
. Additionally, I think it demonstrates the "bias" the type has towards .success
being the "expected" result.
As to typed throws, pretty much all of the discussion I've ever seen about it kept throws
as the untyped version, with the typed version being optional. If that's the case, I don't think anything you've mentioned would be breaking, we'd just need a typed-throws version of unwrapped()
.
Result
has a huge amount of potential convenience API, not just including transformers but recovery and value/error accessors. I specifically made this proposal minimal so as to limit the necessary bikeshedding. Once the type is in use I believe convenience API can be standardized, depending on common use. Adding all possible API in one shot seems untenable to me.
If you want, perhaps you can list the API you most commonly use, including what's already in the proposal? It's good to see, if nothing else.
We'll just have to disagree. The semantics of the type isn't established just by the name of the type, but the names of the cases as well, and the strength of the bias, as you put it, seems much stronger with success
than value
. value
would be more appropriate for Optional
, but some
works even better to establish the semantics of the type.
I was talking about "completeness" as a confusing aspect of Result
, which I've never seen in the community. As to the case names, I sometimes confuse Optional
's .some
for .value
. I don't think that's an indictment of the naming, as I think .some
is superior to .value
for Optional
, but just the common confusion of synonyms that happens to everyone.
Sorry if the case name discussion has become defensive on my part, it's just the part of the proposal I thought was settled, given the community versions and previous discussions.
I don't think previous discussions got far enough along to get into details like bike shedding of case names. You should interpret this as a good sign for the chances of acceptance!
Well I think it would be fair if you could extend the alternative suggestions section of your proposal to mention a few of these suggetions from this thread. This will help the review process, because not all of us read the pitch threads and it will prevent users to rehash the same thing during the review. Furthermore the community can vote their preffered naming scheme.
Fair enough.
That sounds good, I'll add alternate spellings to the proposal soon.
One last bit of bikeshedding that might be useful is any additional protocol conformances we may want. I've already included Equatable
and Hashable
, but I didn't include CustomStringConvertible
, CustomDebugStringConvertible
(as the output already seems okay) or Codable
. SPM adds Codable
conformance for their Result
, which could be interesting built in. I've never needed to persist a Result
, but it seems useful. My only concern is whether there's some standard representation we should use, or whether we should leave it up to the user if there is no standard.
Big +1 from me - I really like where this proposal has landed in terms of the feature set and naming.
Parity with Optional
seems like a good starting point to me as far as conformances go. It'd probably be useful for the standard library to standardize the coding behavior of both Optional
and Result
, but IMO it'd be good to consider that separately, since it involves some amount of coordination and research into existing practice of its own.
To me this is another reason why .some should be the spelling of the success case, and .error for the failure case.
+1 on this, whatever the implementation and/or naming. The only thing I really care about is that functorial and monadic semantics are preserved with the transforming methods, whatever their names end up being: I've no strong feelings on map
versus mapValue
, the only thing I'd add is that I'd love if the bifunctorial nature of Result
was made explicit with a bimap
method.
map
, mapError
, flatMap
and flatMapError
are mandatory, in some form, but I also think that fold
is the essential method to add for a type like this: I think that it would make a lot of sense for Optional
too.
I agree on adding throw
ing transforms when the error associated type is Swift.Error
: that would play nicely with those who make extensive use of the standard Swift error handling, but would want an actual type to work with. They still shouldn't be added on the transformError
methods though, because it would be confusing to be able to return some error both with return
and throw
.
I'd say that parity with Optional
is a good idea: Result
is a wrapper type in the same way Optional
is
It looks like Optional
conforms to CustomDebugStringConvertible
and CustomReflectable
, in addition to Equatable
and Hashable
. I'm guessing its Codable
conformance is somewhere else? Still synthesized?
CustomDebugStringConvertible
seems pretty straightforward, I just modified the Optional
implementation:
extension Result : CustomDebugStringConvertible {
public var debugDescription: String {
var output = "Result."
switch self {
case let .success(value):
output += "success("
debugPrint(value, terminator: "", to: &output)
case let .failure(error):
output += "failure("
debugPrint(error, terminator: "", to: &output)
}
output += ")"
return output
}
}
I've never implemented CustomReflectable
before, so I really have no idea what it's supposed to do:
extension Result : CustomReflectable {
public var customMirror: Mirror {
switch self {
case let .success(value):
return Mirror(
self,
children: [ "success": value ],
displayStyle: .optional)
case let .failure(error):
return Mirror(
self,
children: [ "failure": error ],
displayStyle: .optional)
}
}
}
Additionally, should I add tests for these conformances?
I'm still working on updating the proposal and implementation with the latest suggestions, bug should have new versions up soon.
The Custom*
protocols don't change the API of the type. If the default behavior they customize is acceptable (and it seems like it should be), then there's no need to override them.
I think printing a Result
is better with the DebugCustomStringConvertible
implementation, as otherwise you just get success(#value#)
, as it matches the Optional
behavior a bit better. I have no idea what makes for a good Mirror
so I'm okay leaving that one off.
I've updated the proposal and implementation with styling changes, a bit about alternate spellings, and a CustomDebugStringConvertible
implementation.
I think the alternative spellings could use an expansion on the idea of making Result closely resemble Optional.
enum Result<Wrapped, E> {
case some(Wrapped)
case error(E)
}
Where E could be any spelling.
The only reason I bring this up is because .some would be important if we generalize the ? and if let unwrapping to also work with Result types.
Just a quick thought: Would it make sense to add the following conditional conformance?
extension Result : Swift.Error where Error : Swift.Error {}
So a result can be an error with either an error or a default value wrapped in the success case?!
On the other hand I‘m not entirely sure if the where
clause is necessary at all.
I don't really see how that would be useful. It wouldn't really make sense to throw a Result.success(...)
.
The result already contains this, which should be fine for going back from a result to normal Swift error handling:
extension Result where Error: Swift.Error {
/// 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
}
This seems exactly opposite to me. Result is the type that you can implicitly assign to a variable name and ignore, or throw in a queue for reactive use cases. Result is the one where you might flatmap in more logic (which - doesn’t that break with typed errors in results?)
Errors are what require you to always acknowledge if not deal with the error at a syntactic level. You can delegate up, but you aren’t really meant to be deferring an error - you instead are putting a part of the system into an error state. Deferring an error would be... wrapping it into a result