throws as returning a Result


(Yuta Koshizawa) #1

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 onefunc makeFoo(x: Int) throws -> Foo {
  guard ... else {
    throw FooError()
  }
  return Foo(x)
}// @warn_unused_result// func makeFoo(x: Int) -> Result<Foo> {//
guard ... else {// return Result(error: FooError())// }//
return Result(Foo(x))// }
// Manual propagationlet result: Result<Foo> = makeFoo(42) // without
`try`switch result {
  case let .Success(foo):
    ...
  case let .Failure(error):
    ...
}
// Automatic propagationdo {
  let foo: Foo = try makeFoo(42) // with `try`: a kind of unwrapping
  ...
} catch let error {
  ...
}

<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#for-what>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"
<https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst#automatic-propagation>,
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 wantfunc makeFoo(x: Int) throws -> Foo { ... } // -> Result<Foo>
let a: Result<Foo> = try| makeFoo(42)
  // `try|` for `Result` like `try?` for `Optional`
do {
  let b = try a.throwIfError()
  ...
} catch let error {
  ...
}

If throws were a syntactic sugar of returning a Result, it would be simpler.

// What I wantfunc makeFoo(x: Int) throws -> Foo { ... } // -> Result<Foo>
let a: Result<Foo> = makeFoo(42)
do {
  let b = try a
  ...
} catch let 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
<https://github.com/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.

-- Yuta


Adding Result II: Unconstrained Boogaloo
(Joe Groff) #2

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

-Joe

···

On Mar 14, 2016, at 5:15 AM, Thomas Guthrie via swift-evolution <swift-evolution@swift.org> wrote:

On 14 Mar 2016, at 11:36, Ondrej Barina via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

it would be great usage of Result. (there was discussion about it. I
think it was rejected).

Not really sure about: "let a: Result<Foo> = makeFoo(42)"
I can see that this could become more needed/used when we are going
towards async coding in next Swift (4.0?).

Result is mentioned in https://github.com/apple/swift/blob/master/docs/ErrorHandling.rst#manual-propagation-and-manipulation-of-errors (the proposal/doc for swift’s current error handling) and John McCall had this to say about it:

We considered it, had some specifics worked out, and then decided to put it on hold. Part of our reasoning was that it seemed more like an implementation detail of the async / CPS-conversion features we’d like to provide than an independently valuable feature, given that we don’t want to encourage people to write library interfaces using functional-style error handling instead of throws.

It’s also a feature that’s directly affected by the design of typed throws, which in turn poses some usability challenges for it. For example, without typed throws you really just want the type to be Result<T>. With typed throws, can you still write that, or do you have to write Result<T, ErrorType>? Also, if we want every function result signature to have a corresponding Result<> type, does that permanently prevent us to supporting multiple error types with “typed throws”? Also, would it be too frustrating to work with typed Result values if we don’t allow implicit covariant conversions along one or both dimensions?

(From https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001433.html)


(Thomas Guthrie) #3

it would be great usage of Result. (there was discussion about it. I
think it was rejected).

Not really sure about: "let a: Result<Foo> = makeFoo(42)"
I can see that this could become more needed/used when we are going
towards async coding in next Swift (4.0?).

Result is mentioned in https://github.com/apple/swift/blob/master/docs/ErrorHandling.rst#manual-propagation-and-manipulation-of-errors (the proposal/doc for swift’s current error handling) and John McCall had this to say about it:

We considered it, had some specifics worked out, and then decided to put it on hold. Part of our reasoning was that it seemed more like an implementation detail of the async / CPS-conversion features we’d like to provide than an independently valuable feature, given that we don’t want to encourage people to write library interfaces using functional-style error handling instead of throws.

It’s also a feature that’s directly affected by the design of typed throws, which in turn poses some usability challenges for it. For example, without typed throws you really just want the type to be Result<T>. With typed throws, can you still write that, or do you have to write Result<T, ErrorType>? Also, if we want every function result signature to have a corresponding Result<> type, does that permanently prevent us to supporting multiple error types with “typed throws”? Also, would it be too frustrating to work with typed Result values if we don’t allow implicit covariant conversions along one or both dimensions?

(From https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001433.html)

···

On 14 Mar 2016, at 11:36, Ondrej Barina via swift-evolution <swift-evolution@swift.org> wrote:


(Ondrej Barina) #4

I am for Automatic propagation/conversion.
if this line works:

let a : Result<Foo> = try makeFoo(42)

it would be great usage of Result. (there was discussion about it. I
think it was rejected).

Not really sure about: "let a: Result<Foo> = makeFoo(42)"
I can see that this could become more needed/used when we are going
towards async coding in next Swift (4.0?).

Ondrej B.

···

On Mon, Mar 14, 2016 at 12:07 PM, Yuta Koshizawa via swift-evolution <swift-evolution@swift.org> wrote:

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 {
    throw FooError()
  }
  return Foo(x)
}
// @warn_unused_result
// func makeFoo(x: Int) -> Result<Foo> {
// guard ... else {
// return Result(error: FooError())
// }
// return Result(Foo(x))
// }

// Manual propagation
let result: Result<Foo> = makeFoo(42) // without `try`
switch result {
  case let .Success(foo):
    ...
  case let .Failure(error):
    ...
}

// Automatic propagation
do {
  let foo: Foo = try makeFoo(42) // with `try`: a kind of unwrapping
  ...
} catch let 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>

let a: Result<Foo> = try| makeFoo(42)
  // `try|` for `Result` like `try?` for `Optional`

do {
  let b = try a.throwIfError()
  ...
} catch let 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>

let a: Result<Foo> = makeFoo(42)

do {
  let b = try a
  ...
} catch let 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.

-- Yuta

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dennis Lysenko) #5

+1. The potential integration with auto-async-conversion here is brilliant
and will be incredibly useful if implemented.

···

On Mon, Mar 14, 2016 at 8:15 AM Thomas Guthrie via swift-evolution < swift-evolution@swift.org> wrote:

On 14 Mar 2016, at 11:36, Ondrej Barina via swift-evolution < > swift-evolution@swift.org> wrote:

it would be great usage of Result. (there was discussion about it. I
think it was rejected).

Not really sure about: "let a: Result<Foo> = makeFoo(42)"
I can see that this could become more needed/used when we are going
towards async coding in next Swift (4.0?).

Result is mentioned in
https://github.com/apple/swift/blob/master/docs/ErrorHandling.rst#manual-propagation-and-manipulation-of-errors (the proposal/doc
for swift’s current error handling) and John McCall had this to say about
it:

We considered it, had some specifics worked out, and then decided to put
it on hold. Part of our reasoning was that it seemed more like an
implementation detail of the async / CPS-conversion features we’d like to
provide than an independently valuable feature, given that we don’t want to
encourage people to write library interfaces using functional-style error
handling instead of throws.

It’s also a feature that’s directly affected by the design of typed
throws, which in turn poses some usability challenges for it. For example,
without typed throws you really just want the type to be Result<T>. With
typed throws, can you still write that, or do you have to write Result<T,
>? Also, if we want every function result signature to have a
corresponding Result<> type, does that permanently prevent us to supporting
multiple error types with “typed throws”? Also, would it be too
frustrating to work with typed Result values if we don’t allow implicit
covariant conversions along one or both dimensions?

(From
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001433.html
)
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yuta Koshizawa) #6

This is some supplementary explanations about throws as returning a Result.

   - What's Result?
   - Why we need both throws and Result
   - Result<Value> vs Result<Value, Error>
   - When forgets try
   - With side effects
   - Why not Either, union types nor tuples
   - Return value of do
   - Complication with rethrows

<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#whats-result>What's
Result?

It's an enum declared in a following way.

enum Result<Value> {
  case Success(Value)
  case Failure(ErrorType)
}

It also should have map, flatMap and some convenient methods like Optional.
<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#why-we-need-both-throws-and-result>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.

extension Promise {
  func map<T>(transform: Value -> T) -> Promise<T> { ... }
}

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.

func toInt(x: String) throws -> Int { ... } // -> Result<Int>
let string: Promise<String> = ...let number: Promise<Result<Int>> =
string.map(toInt)

It also caused a problem when I implemented lazily evaluated List<Element>s.

extension List {
  func map<T>(transform: Element -> T) -> List<T> { ... }
}

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>
let strings: List<String> = ... // Infinite listlet numbers:
List<Result<Int>> = strings.map(toInt)let first10: List<Result<Int>> =
numbers.take(10)let result: Result<List<Int>> = sequence(first10) //
List<Result<...>> -> Result<List<...>>do {
  let mapped: List<Int> = try result
  ...
} catch let 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.
<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#resultvalue-vs-resultvalue-error>
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*
<https://github.com/apple/swift-evolution/pull/68> 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,

as it is.

<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#when-forgets-try>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>
let a = toInt(aString) // Compilation error here with Swift 2.Xlet b =
toInt(bString)let sum = a + b // Compilation error here with `throws`
as returning a `Result`

I think it can be eased by improved error messages.
<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#with-side-effects>With
side effects

If a function has side effects, its error should not be ignored implicitly.

func update(x: Int) throws { ... } // -> Result<()>

update(42) // No compilation error => dangerous!!

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.

update(42) // Warning or Error
_ = update(42) // Ignores error explicitly
// Manual propagationswitch update(42) {
  case .Success:
    ...
  case .Failure(error):
    ...
}
// Automatic propagationdo {
  try update(42)
  ...
} catch let error {
  ...
}

<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#why-not-either-union-types-nor-tuples>Why
not Either, union types nor tuples

Result is preferred to Either as discussed on this thread
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/007728.html>
.

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.
<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#return-value-of-do>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>
let sum: Result<Int> = do {
  let a: Int = try toInt("2")
  let b: Int = try toInt("3")
  a + b
} // Result(5)

It can be regarded as a syntactic sugar of nested flatMaps.

However it causes following problems.

   1. It made it impossible to return, break nor continue inside the do
    statement/expression.
   2. Returning the evaluated value of the last expression in braces is not
   Swifty.

I think the following can be the alternative.

let sum: Result<Int> = { () throws -> Int in
  let a: Int = try toInt("2")
  let b: Int = try toInt("3")
  return a + b
}()

<https://gist.github.com/koher/e6a8b128bd7ad6898ac9#complication-with-rethrows>Complication
with rethrows

With throws as returning a Result, what should be the type of the following
numbers?

func toInt(x: String) throws -> Int { ... } // -> Result<Int>
let numbers: ??? = ["one", "2", "3", "four", "5"].map(toInt)

It can be regarded as both Result<List<Int>> and List<Result<Int>>. I think
it should be decided by the type of the numbers.

// Both workslet numbers: Result<Array<Int>> = ["one", "2", "3",
"four", "5"].map(toInt)let numbers: Array<Result<Int>> = ["one", "2",
"3", "four", "5"].map(toInt)

If the type of the numbers are omitted, what happens? We have some options.

   - Compilation error because of the ambiguous type
   - The default type like that the one for integer literals is Int
      - e.g. If map is marked as rethrows, returns Result<Array<Int>>, and
      Array<Result<Int>> for the others.

-- Yuta


(Yuta Koshizawa) #7

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

···

2016-03-15 2:23 GMT+09:00 Joe Groff <jgroff@apple.com>:


(Joe Groff) #8

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.

-Joe

···

On Mar 15, 2016, at 6:39 AM, Yuta Koshizawa <koher@koherent.org> wrote:
2016-03-15 2:23 GMT+09:00 Joe Groff <jgroff@apple.com>:

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


(Yuta Koshizawa) #9

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

···

2016-03-16 2:03 GMT+09:00 Joe Groff <jgroff@apple.com>:

On Mar 15, 2016, at 6:39 AM, Yuta Koshizawa <koher@koherent.org> wrote:
2016-03-15 2:23 GMT+09:00 Joe Groff <jgroff@apple.com>:

-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