You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I think it would be great if throws -> Foo were a syntactic sugar of -> Result<Foo>. Without affecting existing codes, it makes it possible to go back and forth between Manual Propagation and Automatic Propagation seamlessly.
// I wish if the first `makeFoo` were
// a syntactic sugar of the second one
func makeFoo(x:Int)throws->Foo{guard...else{throwFooError()}returnFoo(x)}
// @warn_unused_result
// func makeFoo(x: Int) -> Result<Foo> {
// guard ... else {
// return Result(error: FooError())
// }
// return Result(Foo(x))
// }
// Manual propagation
letresult:Result<Foo>=makeFoo(42) // without `try`
switch result {caselet.Success(foo):...caselet.Failure(error):...}
// Automatic propagation
do{letfoo:Foo=trymakeFoo(42) // with `try`: a kind of unwrapping
...}catchlet error{...}
For what?
I want to unify throws and Result into one feature to keep the language simple.
As referred in "Error Handling Rationale and Proposal", I think Swift should provide something like Result. It means we would have similar two features: throws and Result. We need a way to covert them to each other. For examples, it can be done in the following way.
// What I DON'T want
func makeFoo(x:Int)throws->Foo{...} // -> Result<Foo>
leta:Result<Foo>=try| makeFoo(42)
// `try|` for `Result` like `try?` for `Optional`
do{letb=try a.throwIfError()...}catchlet error{...}
If throws were a syntactic sugar of returning a Result, it would be simpler.
// What I want
func makeFoo(x:Int)throws->Foo{...} // -> Result<Foo>
leta:Result<Foo>=makeFoo(42)do{letb=try a
...}catchlet error{...}
In Addition, it prevents that APIs of third-party libraries diverge. If we had similar but different two features, throws and Result, some libraries would use throws and others would use Result. Actually it has already happened. Some popular libraries use antitypical/Result or their own Result types. If throws were a syntactic sugar of returning a Result, using throws or Result would affect only the appearance of codes, and we could use those libraries in the same way.
It also should have map, flatMap and some convenient methods like Optional.
Why we need both throws and Result
Result provides more flexible way to handle errors than throws though they provide similar functionalities. It can be assigned to a variable, passed to a function and stored in a property while an error must be handled immediately after it is thrown. It is useful especially for asynchronous operations.
For example, think about map or flatMap (or then) method of Promise<Value> (or Future<Value>). They cannot receive a function with throws.
Because the transform is executed asynchronously, this map method cannot throw an error immediately. If we had throws as returning a Result, we can pass a function with throws to the map.
It cannot throw an error because the transform is evaluated lazily. With throws as returning a Result, it could be used with a function with throws too.
func toInt(x:String)throws->Int{...} // -> Result<Int>
letstrings:List<String>=... // Infinite list
letnumbers:List<Result<Int>>= strings.map(toInt)letfirst10:List<Result<Int>>= numbers.take(10)letresult:Result<List<Int>>=sequence(first10) // List<Result<...>> -> Result<List<...>>
do{letmapped:List<Int>=try result
...}catchlet error{...}
If Result is more flexible than throws, why do we need throws? Handling Results manually with manual propagation costs more. We should have a way to handle errors with automatic propagation.
So we need both throws and Result.
Result<Value> vs Result<Value, Error>
I know it is discussed which of untyped throws and typed throws are better. If typed throws is accepted, Result<Value, Error> should be provided instead of Result<Value>
However the proposal about typed throws has been left for several months. I'm not sure if the discussion is continued. So I started this thread with Result<Value>. And even if typed throws is accepted, we just need to change Result<Value> to Result<Value, Error>. The discussion for throws as returning a Result can be applied for typed throws and Result<Value, Error> as it is.
When forgets try
If we forget to write try, what will happen? This is a downside of throws as returning a Result.
Even if we forget to write try, it can raise an compilation error with throws as returning a Result. However the error sites are confusing and nonintuitive.
func toInt(x:String)throws->Int{...} // -> Result<Int>
leta=toInt(aString) // Compilation error here with Swift 2.X
letb=toInt(bString)letsum= a + b // Compilation error here with `throws` as returning a `Result`
I think it can be eased by improved error messages.
With side effects
If a function has side effects, its error should not be ignored implicitly.
So I think throws should add the @warn_unused_result attribute to the function automatically. If we had a kind of @error_unused_result attribute, it would be better.
Result is preferred to Either as discussed on this thread.
Either is a tagged union. However its tags are meaningless: Left and Right. I think it is much better to have something like union types in Ceylon and some other languages than to have Either.
However union types make a significant impact on the type system. Subtyping gets much more complicated. We should also think about intersection types in addition to uniton types. If we had union types, I think it would be better that Optional<Foo> is a sugar of Foo|Nil like in Ceylon. Changing all of them is not practical.
How about tuples? Tuples like (Value?, Error?) is an easy way. However it results four cases: (value, nil), (nil, error), (value, error) and (nil, nil). We don't need the last two.
Therefore I think Result is the best way.
Return value of do
We easily think of a return value of do statement/expression like Haskell's do notation.
func toInt(x:String)throws->Int{...} // -> Result<Int>
letsum:Result<Int>=do{leta:Int=trytoInt("2")letb:Int=trytoInt("3")
a + b
} // Result(5)
It can be regarded as a syntactic sugar of nested flatMaps.
However it causes following problems.
It made it impossible to return, break nor continue inside the do statement/expression.
Returning the evaluated value of the last expression in braces is not Swifty.
I think the following can be the alternative.
letsum:Result<Int>={()throws->Intinleta:Int=trytoInt("2")letb:Int=trytoInt("3")return a + b
}()
Complication with rethrows
With throws as returning a Result, what should be the type of the following numbers?