[swift-evolution] throws as returning a Result

Yuta Koshizawa koher at koherent.org
Wed Mar 16 19:22:15 CDT 2016


2016-03-16 2:03 GMT+09:00 Joe Groff <jgroff at apple.com>:
>
> On Mar 15, 2016, at 6:39 AM, Yuta Koshizawa <koher at koherent.org> wrote:
>
> 2016-03-15 2:23 GMT+09:00 Joe Groff <jgroff at apple.com>:
>
>
> Yeah, we extensively discussed adding a Result type internally, but
> ultimately couldn't justify it. The only real use case we could see in the
> wild was for threading errors through CPS-inversion-style abstractions like
> async promises, something we hope to provide proper language support for.
> More generally, expressing effects as monadic values is a pretty awful
> abstraction; aside from polluting the Internet with an endless deluge of
> unhelpful tutorials, they also don't compose cleanly, they impose nesting
> where is desired—you have to pick between Result<Async<T>> and
> Async<Result<T>>, or build ResultT<AsyncT<Identity>><T> out of monad
> transformers—and they don't do the natural thing when used with other
> higher-order abstractions—if you're mapping a `throws` function over a
> collection, you probably want to propagate that error like `rethrows` does,
> not end up with a collection of Result<T>.
>
>
> Yes, I know the pain of nested monads and I don't want to encourage
> monadic error handling with awful nests.
>
> To tell the truth, I ultimately desire to unify `Optional`s, `throws`
> and `Result`s.
>
> We have already had `Optional`s which can be used in a monadic way. To
> prevent excessive monadic handling, I think we need Automatic
> Propagation for `Optional`s.
>
> ```
> // Automatic Propagation for `Optional`s
> let a: Int? = ...
> let b: Int? = ...
>
> do {
>  let sum: Int = (try a) + (try b)
>  ...
> } catch { // if `a` and/or `b` are `nil`
>  ...
> }
> ```
>
> Although "Error Handling Rational and Proposal" says `Optional`s
> should be used for simple domain errors and are suitable for Manual
> Propagation, I think Automatic Propagation is also useful for
> `Optional`s. We get `nil` not only as errors but also as empty values.
> Our codes are full of `Optional`s. Handling them manually costs a lot.
> So I think it is good to have Automatic Propagation for `Optional`s.
>
> However it is confusing to mix `Optional`s and `throws` functions with
> the same keyword `try`. So I think something like `typealias
> Optional<T> = Result<T, NilError>`, which could be identical in a
> binary form to current `Optional` with `struct NilError: ErrorType
> {}`, and unified `throws` and `Result`s would be better. Then we would
> have only `Result`s, but it could be used as `Optional`s and `throws`.
>
> Although `Result`s might make it possible to abuse monadic error
> handling, problems of abuses are also true for other language
> features: e.g. `(Float, Float)` as `Vector2` instead of `struct
> Vector2 { ... }` for tuples. Even if we keep `Optional`s and `throws`
> separated, `Optional`s can be handled monadically and we need to
> encourage people how and when to use them to prevent abuses. I think
> language features cannot prevent abuses, and it is a role of coding
> guidelines.
>
> So I think it is good to unify `Optional`s, `throws` and `Result`s.
> But because it seemed too radical, I proposed the part of it at first:
> `throws -> Foo` as a syntactic sugar of `-> Result<Foo>`.
>
>
> I agree, it's nice to be able to open optionals and avoid direct monadic
> manipulations too. Optionals can be lifted and extracted from a `throws`
> body fairly easily already, much like a `Result`:
>
> enum Nil: ErrorType { case Nil }
> extension Optional {
>   func getOrThrow() throws -> Wrapped {
>     if let x = self { return x }
>     throw Nil.Nil
>   }
> }
> func doOrNil<T, U>(f: (T) throws -> U, x: T) -> U? {
>   do {
>     return try f(x)
>   } catch {
>     return nil
>   }
> }
>
>
> The latter is provided by the language as `try?` already. If we had typed
> `throws`, it would be nice to be able to express `throws Nil` more
> precisely, of course.

I would still want `try` to unwrap `Optional`s even if we had
`getOrThrow`. Although the difference is slight, I think it is
important. I choose an alternative way, e.g. an applicative style, if
we need some extra works to unwrap `Optional`s besides just writing
`try`.

But I think it is out of scope of this thread: `Result` and `throws. I
will start another thread for it. Thanks for the discussion.

-- Yuta

> -Joe
>
>
> I'd rather see us adopt an
> extensible algebraic effects system, something like http://www.eff-lang.org,
> which provides a framework for `throws`, `async` and other control flow
> effects to be cleanly composed and abstracted over. I see `throws` as the
> first seed of that.
>
>
> Thank you for the information. Because I am not familiar with Eff, I
> will check it. If it composes multiple abstractions well, it must be
> great!
>
> -- Yuta
>
>


More information about the swift-evolution mailing list