[swift-evolution] throws as returning a Result

Yuta Koshizawa koher at koherent.org
Tue Mar 15 08:39:40 CDT 2016


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'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