If the second generic parameter is not constrained to Error
, then this isn’t really Result
, just a mis-named Either
.
Compared to Either
, Result
still makes the "bias" clear as to what's considered a good vs. bad result, even without the Error constraint.
Imho naming is the big flaw of Either
: No matter how you call the cases, they are not supposed to convey any meaning.
As it is extremely easy to declare a type with Either
-semantic and meaningful names, I don't think there's any benefit in having such an arbitrary enum.
Result
, on the other hand, has a clear meaning, and constraining the failure
-case just limits what you can express (or should express - as you can make every type conform to Error
with a single line of code).
Optional<T>
, for example, is more or less equivalent to Result<T, Void>
, and you can think of other cases where something might not work as expected, and where you don't care for the reason of the failure, but rather want to provide other information:
- A failed upload could tell you how many bytes have been transferred, or how you can resume the process
SortedArray.insert(element: Element, position: Int) -> Result<Void, Int>
could simply be successful when the requested index is valid, and return the actual position if your hint was wrong- Even the somewhat odd
Result<Void, Void>
could be useful as an explicit alternative for returningBool
Such examples might be considered as antipatterns and misuse of Result
, but imho it's ok when this type is more than an asynchronous try/catch
.
Quick questions about the 'potential' typed throw future of Swift with the compatibility to the proposed type (I'd really appreciate if also someone from the core team could provide some bikeshedding answer to this questions):
If we had typed throws, would it be possible to upgrade the following method in a non-breaking fashion or would we require a separat method?
extension Result where Error: Swift.Error {
public func unwrapped() throws -> Value
}
I think that making it to something like this, would be a breaking change, am I right?
extension Result where Error : Swift.Error {
public func unwrapped() throws(Error) -> Value
}
Also the above extension is missing an init
which is not expressible until there are typed throws in Swift:
extension Result where Error : Swift.Error {
public init(_ throwing: () throws(Error) -> Value)
}
I'm totally fine with the following extension which is unconstrained to a specific Error
type, but the method above feels like it should obey typed throws in the future.
extension Result where Error == Swift.Error {
public init(_ throwing: () throws -> Value)
public func unwrapped() throws -> Value
}
If Error
would be Never
then I'd expect that the unwrapped
method would not require try
in a typed throwing system.
That said, I personally think that method related to my question should be renamed to signal that the Error type is statically lost.
Bikeshedding:
extension Result where Error: Swift.Error {
public func unconstrainedUnwrapped() throws -> Value
}
@Jon_Shier could you provide your reasoning for going with the map
and flatMap
naming scheme for transforming the value of the result instead of mapValue
and flatMapValue
which is aligned with mapError
and flatMapError
, plus it does not burn the methods which 'potentially' can have a generalized meaning in the future of Swift. Sure the my main point here is the word 'potentially', but I'd like to carefully choose the naming scheme which is fair and has a bigger long-term picture in mind.
I know the API surface growed during the discussion therefore the following is just a question if the inclusion on the following methods is needed in the proposal (or at all)!?
extension Result {
public func materialized() -> Result<Result<Value, Error>, Never> {
return .success(self)
}
}
// In the future with parameterized extensions we could do this:
extension<V, E> Result where Value == Result<V, E>, Error == Never {
public func dematerialized() -> Result<V, E>
}
// The `dematerialized` can be worked around today as well:
extension Result where Error == Never {
public func dematerialized<V, E>() -> Result<V, E> where Value == Result<V, E> {
switch self {
case .success(let value):
return value
}
}
}
// These are also very useful when you need to align some types.
extension Result where Value == Never {
public func promoteValue<NewValue>(_: NewValue.Type) -> Result<NewValue, Error> {
switch self {
case .failure(let error):
return .failure(error)
}
}
}
extension Result where Error == Never {
public func promoteError<NewError>(_: NewError.Type) -> Result<Value, NewError> {
switch self {
case .success(let value):
return .success(value)
}
}
}
Actually, in my experience (and I believe conventionally) it is thought of exactly like an Optional
, but with an error payload to describe the failure case. So it really does "wrap" a value. I'm not arguing for the Wrapped
and some
names that others have but the analogy clearly exists and is very reasonable.
I very strongly disagree with this. value
is a perfectly good case name when paired with error
. As noted above, the bias is implicit in the semantics of Result
. If this pitch were for Either
that would not be the case, but we're not talking about Either
, we're talking about Result
.
I think this is the strongest argument in favor of the spelling you prefer.
This is the spelling I prefer, but I do agree with @Jon_Shier that Result
itself lightly implies completeness. I'm not opposed to success
and failure
on the grounds of implying completeness. I just have trouble remembering what the case names are when I work in projects that use these case names and never have trouble when working in projects that use value
and error
. This is a sample size of one and may not generalize, but if I have trouble remembering them I imagine others might as well.
Again, only a sample size of one, but I have mentioned in this thread that I find these case names hard to remember. This isn't a significant enough problem for me to have ever complained about it before. It's easy enough to look up the case names when necessary.
I think a bikeshed discussion on the names is an appropriate (and inevitable) part of the SE process. Of course as the author, you should certainly put forward the names you prefer.
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.