Add value property to Result

This seems to me like a reason to call it value, in order to provide an optional for the success case (in fact this is a common extension to Result, including in the unit test for this very feature).

This is separate from whether it is worth providing a migration from a function to a property.

Wouldn't that rather be:

if let foo = try? result.success.foo

Haha true… but then isn't that also an implication that var success: Success? should be public? If it's so common (which it sure seems to be), then shouldn't we be considering exposing that instead?

Ha, you're right. I'm surprised; from what I remember, that didn't use to work.

Since it appears to in my tests, I object less to this. :) (I still think the name is wrong though, and that if var success: Success? is so common then we should be considering that instead)

Yes, it probably is. The answer to why it isn't lies in the acceptance for SE-0235, about which not much has changed since:

Case accessors are a more complex question. The current proposal doesn't include case accessors, but the original proposal did, and we're anticipating a future proposal that will add automatic synthesis for case accessors. That synthesis will undoubtedly use names based on the actual case names, so at a first glance, it seems imprudent to use case names that we're not sure we'd want for the accessors. However, feedback on this review has led the Core Team to more seriously consider this issue, and it now seems quite complicated. A name that's a good name for a particular state of an enum (like failure ) might not be a good name for a value carried in that case (like error ). It may be true that case accessor names will always end up feeling a little contrived, and so it's just better to use the actual case names because they're easy to interpret. More importantly, we don't want to burden this proposal because we haven't resolved those questions yet to our satisfaction.

Fair enough.

Given @DevAndArtist's correction to my syntax and my realization that this is a different way of accessing the entire result (not just one case, since this returns either the success or the failure value), I withdraw my objections.

Thanks for humoring me as I stumbled through this.

+1 sounds good to me :sweat_smile:

7 Likes

I’m in favor of this. Not much else to say on the contents of the proposal.

As to the process, though, given that it was the core team that inserted get() in place of unwrapped() on its own accord into the design of Result, writing that it expected a future type to use the same API, and then the core team that inserted value on its own accord in place of get() into the design of that very API they envisioned before, what is the role of the community in addressing this issue? :man_shrugging:

4 Likes

+1 this is a nice win! It makes the past version more in-line with what we have learned is more ergonomic.

1 Like

This proposal might seem innocuous, but it's actually demonstrative of a lack of standardization of dependency-injected get accessors. Something needs to be done here, but proceed with caution, because you're setting a precedent for a pattern that's going to grow in popularity now that properties are growing up.

"get"?
https://developer.apple.com/documentation/swiftui/binding/init(get:set:)-7ufcp
"catching body"?
https://developer.apple.com/documentation/swift/result/3139399-init
…"value"?

I say the compiler needs to be improved first, to support Result being elevated to property wrapper status. Then the naming will take care of itself.

Here's what we need:

public var wrappedValue: Success {
  get throws { try get() }
  set { self = .success(newValue) }
}

Here's why we can't have that yet:

  1. // Property wrappers currently cannot define an 'async' or 'throws' accessor
  2. 'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'

The get method can be deprecated after get/set accessors are made accessible as closures.

1 Like

I like this addition, as long as it doesn't interfere with the possible future addition of typed throws. Losing the Failure type here permanently would be unfortunate.

As suggested previously, it would be a good idea to roll this change together with previously proposed changes to support an async closure initializer and other enhancements to better support the concurrency features.

5 Likes

With typed throws, Result will be functionally no different than its own get method, aside from parentheses. The get method is the success's get accessor—it's a method because accessors are not available in Swift as closures. A type that is synonymous with one of its properties (proposed here as value) is a property wrapper.

struct S {
  struct Error: Swift.Error { }

  @Result(Error.self) var value = 1

  mutating func reassignValue() {
    let one = try? value
    value = 2
    $value = .failure(.init())
  }
}
Compiling but not useful implementation for that
@propertyWrapper public enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)

  public var wrappedValue: Success {
    get { // `throws` belongs here.
      switch self {
      case let .success(success):
        return success
      case let .failure(failure):
        fatalError() // `throw failure` doesn't work yet.
      }
    }
    set { self = .success(newValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

public extension Result {
  init(wrappedValue: Success, _: Failure.Type) {
    self = .success(wrappedValue)
  }
}

I see your point, but I don't think this behavior makes sense as a property wrapper. A Result is a value, not a set of behaviors. To make it so blurs the line too much I think. I do think such equivalence could be useful in some scenarios, I just don't think property wrappers are the way to go. And I don't think such evolution is blocked by this proposal.

3 Likes
  1. If typed throws had existed in Swift 1, we would not have both Optional and Result. They're the same thing (two caches, instead of a "get" closure for both), separated only because of still-missing features.
  2. Optional would have been a property wrapper instead of compiler magic.
1 Like

I see no reason why either of those would be true.

9 Likes

+1 this is great. I agree that .value is a good name for this. see below

-Chris

1 Like

I second with @Jon_Shier here in all the points. I also don‘t see how typed throws would have eliminated the need for Result and Optional in the early days as those types serve different needs (optionality is not always an error but just the complier guaranteed absence of a value).

I hope the core team considered the situation in the future iff we get typed throws that there will be a mechanism to make the throws effect which today returns only Error, to return Failure instead.

If the above is not possible, then the current shift from get() to value is being made too early as we‘d burn the opportunity to expose the correct Failure type.

8 Likes

Aha, yes, I missed that. I agree that the right type for this should be:

extension Result {
  public var value: Success {
    get throws Failure {   // typed throw here.

I don't see how we could retroactively fix this with an addition of typed throws in the future (without a major source break) so waiting for that seems pragmatic.

-Chris

16 Likes

Yep, I agree we should wait for the resolution of typed throws.

4 Likes

Counterpoint: typed throws is actively harmful at the public-API level, and we should not delay improvements to the standard library in order to accommodate a hypothetical future anti-pattern.

3 Likes

While I can see the argument that using typed throws in public methods may not be advisable (anti pattern seems hyperbolic), this use on Result should be largely uncontroversial given it exposes type information that already exists. If the user wants to erase the error there they can simply erase the Result.

8 Likes