[Concurrency] async/await + actors

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

This looks a little odd to me, not not extremely clear as to what is happening. Under what conditions will we get a nil instead of a Foo? Maybe it’s just me or that the syntax is new and I’ll get used to it.

But it gets even more complicated if we have:

func getAFoo() async -> Foo {
   …
}

func getABar() async(nonthrowing) -> Bar {
   …
}

Now, we have an odd situation where the ‘await’ keyword may sometimes accept ?/! but in other cases may not (or it has no meaning):

guard
   let foo = await? getAFoo(),
   let bar = await? getABar() // Is this an error?? If not, what does it mean?
   else { … }

Since this edge of throws/try wasn’t explicitly covered in the write-up (or did I miss it?), was wondering about your thoughts.

—Karim

···

Date: Thu, 17 Aug 2017 15:24:14 -0700
From: Chris Lattner <clattner@nondot.org>
To: swift-evolution <swift-evolution@swift.org>
Subject: [swift-evolution] [Concurrency] async/await + actors

Hi all,

As Ted mentioned in his email, it is great to finally kick off discussions for what concurrency should look like in Swift. This will surely be an epic multi-year journey, but it is more important to find the right design than to get there fast.

I’ve been advocating for a specific model involving async/await and actors for many years now. Handwaving only goes so far, so some folks asked me to write them down to make the discussion more helpful and concrete. While I hope these ideas help push the discussion on concurrency forward, this isn’t in any way meant to cut off other directions: in fact I hope it helps give proponents of other designs a model to follow: a discussion giving extensive rationale, combined with the long term story arc to show that the features fit together.

Anyway, here is the document, I hope it is useful, and I’d love to hear comments and suggestions for improvement:
https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782

-Chris

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

-Chris

···

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

I’d be curious to see numbers on the prevalence of try?/! across various kinds of codebases (i.e.: library code, app code, CLI utils, etc). I don’t use try? all that much, but I have used it. I also have found (IMHO) legitimate uses for try! in my unit tests where I want the test logic to be brittle with respect to expected conditions surrounding the test—to fail immediately if those conditions are not as I intend them to be.

If we can write "try? await foo()” (which I think is the right way to position this), will we be able to also write "try await foo()” ? I’d hope that the latter wouldn’t be prohibited… as I think it would just make the former less discoverable. If I’m learning Swift and I see “try? await” on one line, and nearby just “await” it’s easier to assume that the first can produce an error and the second can’t, no?

If I’m honest, I *think* I’d rather we'd always have to be explicit about both try and await… yes it’s more to type, but it’s also a lot clearer about what exactly is happening. And if I always use "do try catch” together it forms a consistent pattern that’s easier to read and easier to learn. If sometimes it’s “do try catch” and sometimes "do await catch” and sometimes just “await" it seems to me we’ve lost some clarity and it’s harder for me to compose my understanding of the rules.

On the other hand, I can’t say I’ve given this a lot of deep thought either :slight_smile:

—Karim

···

On Aug 19, 2017, at 7:17 PM, Chris Lattner <clattner@nondot.org> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

-Chris

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

That seems like an argument in favor of having async and throws as orthogonal concepts.

···

On 20 Aug 2017, at 01:17, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org> wrote:

-Chris

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

try? and try! are quite common from what I've seen.

John.

···

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

As analogous to `throws` and `try`, I think we have an option that `await!`
means blocking.

First, if we introduce something like `do/catch` for `async/await`, I think
it should be for blocking. For example:

do {
  return await foo()
} block

It is consistent with `do/try/catch` because it should allow to return a
value from inside `do` blocks for an analogy of `throws/try`.

// `throws/try`
func foo() -> Int {
  do {
    return try bar()
  } catch {
    ...
  }
}

// `async/await`
func foo() -> Int {
  do {
    return await bar()
  } block
}

And `try!` is similar to `do/try/catch`.

// `try!`
let x = try! foo()
// uses `x` here

// `do/try/catch`
do {
  let x = try foo()
  // uses `x` here
} catch {
  fatalError()
}

If `try!` is a sugar of `do/try/catch`, it also seems natural that `await!`
is a sugar of `do/await/block`. However, currently all `!` in Swift are
related to a logic failure. So I think using `!` for blocking is not so
natural in point of view of symbology.

Anyway, I think it is valuable to think about what `do` blocks for
`async/await` mean. It is also interesting that thinking about combinations
of `catch` and `block` for `async throws` functions: e.g. If only `block`,
the enclosing function should be `throws`.

That aside, I think `try!` is not so occasional and is so important. Static
typing has limitations. For example, even if we has a text field which
allows to input only numbers, we still get an input value as a string and
parsing it may fail on its type though it actually never fails. If we did
not have easy ways to convert such a simple domain error or a recoverable
error to a logic failure, people would start ignoring them as we has seen
in Java by `catch(Exception e) {}`. Now we have `JSONDecoder` and we will
see much more `try!` for bundled JSON files in apps or generated JSONs by
code, for which decoding fails as a logic failure.

···

2017-08-21 2:20 GMT+09:00 John McCall via swift-evolution < swift-evolution@swift.org>:

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution < > swift-evolution@swift.org> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the
Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the
"async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we
want to suppress the catch block with ?/!, does that mean we do it on the
‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and
await!”. My sense is that the try? and try! forms are only occasionally
used, and await? implies heavily that the optional behavior has something
to do with the async, not with the try. I think it would be ok to have to
write “try? await foo()” in the case that you’d want the thrown error to
turn into an optional. That would be nice and explicit.

try? and try! are quite common from what I've seen.

--
Yuta

2017-08-21 2:20 GMT+09:00 John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

try? and try! are quite common from what I've seen.

As analogous to `throws` and `try`, I think we have an option that `await!` means blocking.

First, if we introduce something like `do/catch` for `async/await`, I think it should be for blocking. For example:

do {
  return await foo()
} block

It is consistent with `do/try/catch` because it should allow to return a value from inside `do` blocks for an analogy of `throws/try`.

// `throws/try`
func foo() -> Int {
  do {
    return try bar()
  } catch {
    ...
  }
}

// `async/await`
func foo() -> Int {
  do {
    return await bar()
  } block
}

And `try!` is similar to `do/try/catch`.

// `try!`
let x = try! foo()
// uses `x` here

// `do/try/catch`
do {
  let x = try foo()
  // uses `x` here
} catch {
  fatalError()
}

If `try!` is a sugar of `do/try/catch`, it also seems natural that `await!` is a sugar of `do/await/block`. However, currently all `!` in Swift are related to a logic failure. So I think using `!` for blocking is not so natural in point of view of symbology.

Anyway, I think it is valuable to think about what `do` blocks for `async/await` mean. It is also interesting that thinking about combinations of `catch` and `block` for `async throws` functions: e.g. If only `block`, the enclosing function should be `throws`.

Personally, I think these sources of confusion are a good reason to keep the feature separate.

The idea of using await! to block a thread is interesting but, as you say, does not fit with the general meaning of ! for logic errors. I think it's fine to just have an API to block waiting for an async operation, and we can choose the name carefully to call out the danger of deadlocks.

John.

···

On Aug 20, 2017, at 3:56 PM, Yuta Koshizawa <koher@koherent.org> wrote:

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

That aside, I think `try!` is not so occasional and is so important. Static typing has limitations. For example, even if we has a text field which allows to input only numbers, we still get an input value as a string and parsing it may fail on its type though it actually never fails. If we did not have easy ways to convert such a simple domain error or a recoverable error to a logic failure, people would start ignoring them as we has seen in Java by `catch(Exception e) {}`. Now we have `JSONDecoder` and we will see much more `try!` for bundled JSON files in apps or generated JSONs by code, for which decoding fails as a logic failure.

--
Yuta

Thought about it in more depth, and I’m now firmly in the camp of: ‘throws’/‘try' and ‘async’/‘await' should be orthogonal features. I think the slight call-site reduction in typed characters ('try await’ vs ‘await’) is heavily outweighed by the loss of clarity on all the edge cases.

—Karim

···

On Aug 21, 2017, at 1:56 PM, John McCall <rjmccall@apple.com> wrote:

On Aug 20, 2017, at 3:56 PM, Yuta Koshizawa <koher@koherent.org <mailto:koher@koherent.org>> wrote:

2017-08-21 2:20 GMT+09:00 John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

try? and try! are quite common from what I've seen.

As analogous to `throws` and `try`, I think we have an option that `await!` means blocking.

First, if we introduce something like `do/catch` for `async/await`, I think it should be for blocking. For example:

do {
  return await foo()
} block

It is consistent with `do/try/catch` because it should allow to return a value from inside `do` blocks for an analogy of `throws/try`.

// `throws/try`
func foo() -> Int {
  do {
    return try bar()
  } catch {
    ...
  }
}

// `async/await`
func foo() -> Int {
  do {
    return await bar()
  } block
}

And `try!` is similar to `do/try/catch`.

// `try!`
let x = try! foo()
// uses `x` here

// `do/try/catch`
do {
  let x = try foo()
  // uses `x` here
} catch {
  fatalError()
}

If `try!` is a sugar of `do/try/catch`, it also seems natural that `await!` is a sugar of `do/await/block`. However, currently all `!` in Swift are related to a logic failure. So I think using `!` for blocking is not so natural in point of view of symbology.

Anyway, I think it is valuable to think about what `do` blocks for `async/await` mean. It is also interesting that thinking about combinations of `catch` and `block` for `async throws` functions: e.g. If only `block`, the enclosing function should be `throws`.

Personally, I think these sources of confusion are a good reason to keep the feature separate.

The idea of using await! to block a thread is interesting but, as you say, does not fit with the general meaning of ! for logic errors. I think it's fine to just have an API to block waiting for an async operation, and we can choose the name carefully to call out the danger of deadlocks.

John.

That aside, I think `try!` is not so occasional and is so important. Static typing has limitations. For example, even if we has a text field which allows to input only numbers, we still get an input value as a string and parsing it may fail on its type though it actually never fails. If we did not have easy ways to convert such a simple domain error or a recoverable error to a logic failure, people would start ignoring them as we has seen in Java by `catch(Exception e) {}`. Now we have `JSONDecoder` and we will see much more `try!` for bundled JSON files in apps or generated JSONs by code, for which decoding fails as a logic failure.

--
Yuta

Thought about it in more depth, and I’m now firmly in the camp of:
‘throws’/‘try' and ‘async’/‘await' should be orthogonal features. I think
the slight call-site reduction in typed characters ('try await’ vs ‘await’)
is heavily outweighed by the loss of clarity on all the edge cases.

My concern is less for' ‘throws’/‘async' in declarations and ‘try’/‘await'
at call sites ( I can live with both for clarity and explicitness) than it
is for the enclosing 'beginAsync/do'. Johnathan Hull made a similar point
on another thread
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170821/039142.html>
.

The principle at work, that aligns with the concurrency manifesto 'design'
section, is that it should not be the case that handling errors is more
onerous than ignoring or discarding them. Nor should handling errors
produce ugly code.

If handling errors requires nesting a do/try/catch in a beginAsync block
every time, then code that ignored errors will always look cleaner than
responsible code that handles them. Not handling errors will be the
default. If there are specific recoverable errors, like moving a file from
a download task only to find something else already moved it there, now
there are multiple nested do/try/catch.

(I'm not sure what the reasoning is that most users won't have to interact
with primitive `beginAsync`. That seems like it would be the starting
point for every use of any asynchronous function, especially in iOS
development, as seen in the IBAction example)

Leaving explicit throws/async in declarations and try/await at call sites,
a narrower modification would be:

1. Make every `beginAsync` coroutine start also a `do` block. The `catch`
block would be necessary only if something throws-- currently a `do` block
can be used this way, so it matches nicely.

(This is the same as Johnathan Hull noted on the thread, that if a goal is
to eliminate the pyramid of doom, requiring two levels of indentation to do
anything isn't a clear win.)

2. The two syspendAsync calls should be only the one:
func suspendAsync<T>(
    _ body: (_ continuation: @escaping (T) -> (),
    _ error: @escaping (Error) -> ()) -> ()
) async throws -> T

We can assume the programmers using this function basically know what
they're doing (not the same as being unwilling to cut corners, so make them
cut corners explicitly and visibly). If a user of this function knows that
no errors are possible, then then it should be called with try! and the
error closure ignored, presumably by replacing it with `_`. To force `try!
suspendAsync` and then call the error closure would be a programmer error.
It would also be a programmer error to keep the throwing function and then
not call the error block, but they can do that with the API as proposed--that's
just harder with the API as proposed because the single-continuation
suspend method makes it easy to write code that ignores errors.

We are talking not only about a language change, but making all Swift
programmers adopt a new methodology. It's an opportunity to build new
habits, as noted in the manifesto, by placing the right thing to do at
hand. Two years ago, moving from passing a pointer to an NSError to
do-try-catch--and truly no one ever used that pattern outside Cocoa
frameworks, a crisis-level problem in error handling-- it was a huge
obvious win. It made dealing with errors so much better. Completion blocks
are not at the level of NSError-pointer-pointer level crisis, but
regardless this should have similar improvement when doing the right thing.

(And my opinion on try?/try!: `try?` I seldom see used, find it be an
anti-pattern of ignoring errors instead of explicitly recovering; I
actually wish it wasn't in the language, but guess some people find
it useful. `try!` is necessary and useful for cases where the compiler
can't guarantee success and the programmer is willing to assert it's not
going to fail, and `!` marks those points in code nicely, matching the
optional syntax.)

About potential await? and await!: If we kept call sites `try await` (or
`await try`?) then the `try?/try!` semantics would behave the same,
another argument for that. I assume `await!` would have the current queue
block until the function returns.

The stronger need is for better recognition that often async functions will
_sometimes_ have their values or errors immediately, if cached, or known to
be impossible to get. In promise/futures, this is the equivalent of
creating a future already fulfilled with a value or error. This might just
be an educational point, though.

Maybe the use of `await?` could be this, checking if the value exists
already-- fulfill the value if it can without suspending, but return nil if
not--throwing if known error, unless try? also so annotated.

Really interesting and insightful comments on this thread, look forward to
seeing how this further evolves.

Mike Sanderson

···

On Mon, Aug 21, 2017 at 4:09 PM, Karim Nassar via swift-evolution < swift-evolution@swift.org> wrote:

—Karim

On Aug 21, 2017, at 1:56 PM, John McCall <rjmccall@apple.com> wrote:

On Aug 20, 2017, at 3:56 PM, Yuta Koshizawa <koher@koherent.org> wrote:

2017-08-21 2:20 GMT+09:00 John McCall via swift-evolution <swift-evoluti
on@swift.org>:

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution < >> swift-evolution@swift.org> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution < >> swift-evolution@swift.org> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the
Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the
"async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we
want to suppress the catch block with ?/!, does that mean we do it on the
‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and
await!”. My sense is that the try? and try! forms are only occasionally
used, and await? implies heavily that the optional behavior has something
to do with the async, not with the try. I think it would be ok to have to
write “try? await foo()” in the case that you’d want the thrown error to
turn into an optional. That would be nice and explicit.

try? and try! are quite common from what I've seen.

As analogous to `throws` and `try`, I think we have an option that
`await!` means blocking.

First, if we introduce something like `do/catch` for `async/await`, I
think it should be for blocking. For example:

do {
  return await foo()
} block

It is consistent with `do/try/catch` because it should allow to return a
value from inside `do` blocks for an analogy of `throws/try`.

// `throws/try`
func foo() -> Int {
  do {
    return try bar()
  } catch {
    ...
  }
}

// `async/await`
func foo() -> Int {
  do {
    return await bar()
  } block
}

And `try!` is similar to `do/try/catch`.

// `try!`
let x = try! foo()
// uses `x` here

// `do/try/catch`
do {
  let x = try foo()
  // uses `x` here
} catch {
  fatalError()
}

If `try!` is a sugar of `do/try/catch`, it also seems natural that
`await!` is a sugar of `do/await/block`. However, currently all `!` in
Swift are related to a logic failure. So I think using `!` for blocking is
not so natural in point of view of symbology.

Anyway, I think it is valuable to think about what `do` blocks for
`async/await` mean. It is also interesting that thinking about combinations
of `catch` and `block` for `async throws` functions: e.g. If only `block`,
the enclosing function should be `throws`.

Personally, I think these sources of confusion are a good reason to keep
the feature separate.

The idea of using await! to block a thread is interesting but, as you say,
does not fit with the general meaning of ! for logic errors. I think it's
fine to just have an API to block waiting for an async operation, and we
can choose the name carefully to call out the danger of deadlocks.

John.

That aside, I think `try!` is not so occasional and is so important.
Static typing has limitations. For example, even if we has a text field
which allows to input only numbers, we still get an input value as a string
and parsing it may fail on its type though it actually never fails. If we
did not have easy ways to convert such a simple domain error or a
recoverable error to a logic failure, people would start ignoring them as
we has seen in Java by `catch(Exception e) {}`. Now we have `JSONDecoder`
and we will see much more `try!` for bundled JSON files in apps or
generated JSONs by code, for which decoding fails as a logic failure.

--
Yuta

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

I agree.

1. `async(nonthrowing)` and `async` as a subtype of `throws` can be an
obstacle when we want to add the third effect following `throws` and
`async` though I think `async(nonthrowing)` is an interesting idea.
Assuming that the new effect is named `foos`, we may want
`async(nonfooing)` or to make `async` be a subtype of `foos`. But it
is hard because they are destructive. We need to modify all code which
uses `async`. However, it is inconsistent with `throws` to give up
them and make `foos` orthogonal to `async`.

2. It is also true for `throws`. If we had introduced `async/await`
before `throws/try` were introduced, it would be hard to introduce
`async(nonthrowing)` or `async` as a subtype of `throws` because they
are destructive. (Although `async/await` without `throws/try` seems
impractical, it is not impossible by something like `func bar() async
-> Result<Foo>`)

So I think `async` and `throws` are essentially orthogonal, and just
factually used together in most cases. I guess choosing the essential
one will keep the language simpler and prevent unexpected problems in
the future.

···

On Aug 21, 2017, at 1:56 PM, John McCall <rjmccall@apple.com> wrote:

Personally, I think these sources of confusion are a good reason to keep the feature separate.

The idea of using await! to block a thread is interesting but, as you say, does not fit with the general meaning of ! for logic errors. I think it's fine to just have an API to block waiting for an async operation, and we can choose the name carefully to call out the danger of deadlocks.

John.

2017-08-22 5:08 GMT+09:00 Karim Nassar <karim@karimnassar.com>:

Thought about it in more depth, and I’m now firmly in the camp of: ‘throws’/‘try' and ‘async’/‘await' should be orthogonal features. I think the slight call-site reduction in typed characters ('try await’ vs ‘await’) is heavily outweighed by the loss of clarity on all the edge cases.

—Karim

--
Yuta

Thought about it in more depth, and I’m now firmly in the camp of: ‘throws’/‘try' and ‘async’/‘await' should be orthogonal features. I think the slight call-site reduction in typed characters ('try await’ vs ‘await’) is heavily outweighed by the loss of clarity on all the edge cases.

+1

-Thorsten

···

Am 21.08.2017 um 22:09 schrieb Karim Nassar via swift-evolution <swift-evolution@swift.org>:

—Karim

On Aug 21, 2017, at 1:56 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Aug 20, 2017, at 3:56 PM, Yuta Koshizawa <koher@koherent.org <mailto:koher@koherent.org>> wrote:

2017-08-21 2:20 GMT+09:00 John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Aug 19, 2017, at 7:17 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 19, 2017, at 8:14 AM, Karim Nassar via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This looks fantastic. Can’t wait (heh) for async/await to land, and the Actors pattern looks really compelling.

One thought that occurred to me reading through the section of the "async/await" proposal on whether async implies throws:

If ‘async' implies ‘throws' and therefore ‘await' implies ‘try’, if we want to suppress the catch block with ?/!, does that mean we do it on the ‘await’ ?

guard let foo = await? getAFoo() else { … }

Interesting question, I’d lean towards “no, we don’t want await? and await!”. My sense is that the try? and try! forms are only occasionally used, and await? implies heavily that the optional behavior has something to do with the async, not with the try. I think it would be ok to have to write “try? await foo()” in the case that you’d want the thrown error to turn into an optional. That would be nice and explicit.

try? and try! are quite common from what I've seen.

As analogous to `throws` and `try`, I think we have an option that `await!` means blocking.

First, if we introduce something like `do/catch` for `async/await`, I think it should be for blocking. For example:

do {
  return await foo()
} block

It is consistent with `do/try/catch` because it should allow to return a value from inside `do` blocks for an analogy of `throws/try`.

// `throws/try`
func foo() -> Int {
  do {
    return try bar()
  } catch {
    ...
  }
}

// `async/await`
func foo() -> Int {
  do {
    return await bar()
  } block
}

And `try!` is similar to `do/try/catch`.

// `try!`
let x = try! foo()
// uses `x` here

// `do/try/catch`
do {
  let x = try foo()
  // uses `x` here
} catch {
  fatalError()
}

If `try!` is a sugar of `do/try/catch`, it also seems natural that `await!` is a sugar of `do/await/block`. However, currently all `!` in Swift are related to a logic failure. So I think using `!` for blocking is not so natural in point of view of symbology.

Anyway, I think it is valuable to think about what `do` blocks for `async/await` mean. It is also interesting that thinking about combinations of `catch` and `block` for `async throws` functions: e.g. If only `block`, the enclosing function should be `throws`.

Personally, I think these sources of confusion are a good reason to keep the feature separate.

The idea of using await! to block a thread is interesting but, as you say, does not fit with the general meaning of ! for logic errors. I think it's fine to just have an API to block waiting for an async operation, and we can choose the name carefully to call out the danger of deadlocks.

John.

That aside, I think `try!` is not so occasional and is so important. Static typing has limitations. For example, even if we has a text field which allows to input only numbers, we still get an input value as a string and parsing it may fail on its type though it actually never fails. If we did not have easy ways to convert such a simple domain error or a recoverable error to a logic failure, people would start ignoring them as we has seen in Java by `catch(Exception e) {}`. Now we have `JSONDecoder` and we will see much more `try!` for bundled JSON files in apps or generated JSONs by code, for which decoding fails as a logic failure.

--
Yuta

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

Just wanted to sum up my actors interrogation here:

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

2. Clarification about whether we are called back on the actor's queue after awaiting on some other code/actor.

3. How do we differentiate between void-returning methods that can be awaited and void-returning methods that are oneway "fire and forget". These two methods written as of now:

fund doSomething(completionHandler: () -> ()) -> Void
func doSomething() -> Void

Currently they'd translate both to:

actor func doSomething() -> Void

Thomas

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

Thomas

···

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

If you wrap the call to a long-running operation of another actor in a `beginAsync`, I would assume that other `actor funcs` of your actor will be able to run even while
the long-running operation is pending:

actor class Caller {
  let callee = Callee()
  var state = SomeState()

  actor func foo() {
    beginAsync {
      let result = await callee.longRunningOperation()
      // do something with result and maybe state
    }
  }
  actor func bar() {
    // modify actor state
  }
}

Note, that in this case while waiting asynchronously on the long-running operation, the state of the caller might get changed by another of its `actor funcs` running.
Sometimes this might be intended - e.g. for cancellation - but it also could lead to hard to find bugs...

···

Am 23.08.2017 um 12:29 schrieb Thomas via swift-evolution <swift-evolution@swift.org>:

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thomas

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

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

If you wrap the call to a long-running operation of another actor in a `beginAsync`, I would assume that other `actor funcs` of your actor will be able to run even while
the long-running operation is pending:

actor class Caller {
  let callee = Callee()
  var state = SomeState()

  actor func foo() {
    beginAsync {
      let result = await callee.longRunningOperation()
      // do something with result and maybe state
    }
  }
  actor func bar() {
    // modify actor state
  }
}

As currently proposed, the “// do something with result and maybe state” line would likely run on Callee’s queue, not Caller’s queue. I still strongly believe that this behavior should be reconsidered.

It does, however, bring up some interesting questions about how actors interact with themselves. One of the rules laid out in Chris’s document says that “local state and non-actor methods may only be accessed by methods defined lexically on the actor or in an extension to it (whether they are marked actor or otherwise).” That means it would be allowed for the code in foo() to access state and call non-actor methods, even after the await. As proposed that would be unsafe, and since the queue is an implementation detail inaccessible to your code there wouldn’t be a straightforward way to get back on the right queue to make it safe. I presume you could call another actor method to get back on the right queue, but having to do that explicitly after every await in an actor method seems tedious and error prone.

In order to have strong safety guarantees for actors you would want to ensure that all the code that has access to the state runs on the actor’s queue. There are currently two holes I can think of that would prevent us from having that protection: await and escaping blocks. await could be made safe if it were changed to return to the calling queue. Maybe escaping blocks could be restricted to only calling actor methods.

···

On Aug 23, 2017, at 4:28 PM, Marc Schlichte via swift-evolution <swift-evolution@swift.org> wrote:

Am 23.08.2017 um 12:29 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Note, that in this case while waiting asynchronously on the long-running operation, the state of the caller might get changed by another of its `actor funcs` running.
Sometimes this might be intended - e.g. for cancellation - but it also could lead to hard to find bugs...

Thomas

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

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

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

If you wrap the call to a long-running operation of another actor in a `beginAsync`, I would assume that other `actor funcs` of your actor will be able to run even while
the long-running operation is pending:

actor class Caller {
  let callee = Callee()
  var state = SomeState()

  actor func foo() {
    beginAsync {
      let result = await callee.longRunningOperation()
      // do something with result and maybe state
    }
  }
  actor func bar() {
    // modify actor state
  }
}

As currently proposed, the “// do something with result and maybe state” line would likely run on Callee’s queue, not Caller’s queue. I still strongly believe that this behavior should be reconsidered.

It does, however, bring up some interesting questions about how actors interact with themselves. One of the rules laid out in Chris’s document says that “local state and non-actor methods may only be accessed by methods defined lexically on the actor or in an extension to it (whether they are marked actor or otherwise).” That means it would be allowed for the code in foo() to access state and call non-actor methods, even after the await. As proposed that would be unsafe, and since the queue is an implementation detail inaccessible to your code there wouldn’t be a straightforward way to get back on the right queue to make it safe. I presume you could call another actor method to get back on the right queue, but having to do that explicitly after every await in an actor method seems tedious and error prone.

In order to have strong safety guarantees for actors you would want to ensure that all the code that has access to the state runs on the actor’s queue. There are currently two holes I can think of that would prevent us from having that protection: await and escaping blocks. await could be made safe if it were changed to return to the calling queue. Maybe escaping blocks could be restricted to only calling actor methods.

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Cheers
Marc

···

Am 24.08.2017 um 01:56 schrieb Adam Kemp <adam.kemp@apple.com>:

On Aug 23, 2017, at 4:28 PM, Marc Schlichte via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Am 23.08.2017 um 12:29 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Note, that in this case while waiting asynchronously on the long-running operation, the state of the caller might get changed by another of its `actor funcs` running.
Sometimes this might be intended - e.g. for cancellation - but it also could lead to hard to find bugs...

Thomas

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

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

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Thomas

···

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com> wrote:

Am 24.08.2017 um 01:56 schrieb Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>>:

On Aug 23, 2017, at 4:28 PM, Marc Schlichte via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Am 23.08.2017 um 12:29 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

If you wrap the call to a long-running operation of another actor in a `beginAsync`, I would assume that other `actor funcs` of your actor will be able to run even while
the long-running operation is pending:

actor class Caller {
  let callee = Callee()
  var state = SomeState()

  actor func foo() {
    beginAsync {
      let result = await callee.longRunningOperation()
      // do something with result and maybe state
    }
  }
  actor func bar() {
    // modify actor state
  }
}

As currently proposed, the “// do something with result and maybe state” line would likely run on Callee’s queue, not Caller’s queue. I still strongly believe that this behavior should be reconsidered.

It does, however, bring up some interesting questions about how actors interact with themselves. One of the rules laid out in Chris’s document says that “local state and non-actor methods may only be accessed by methods defined lexically on the actor or in an extension to it (whether they are marked actor or otherwise).” That means it would be allowed for the code in foo() to access state and call non-actor methods, even after the await. As proposed that would be unsafe, and since the queue is an implementation detail inaccessible to your code there wouldn’t be a straightforward way to get back on the right queue to make it safe. I presume you could call another actor method to get back on the right queue, but having to do that explicitly after every await in an actor method seems tedious and error prone.

In order to have strong safety guarantees for actors you would want to ensure that all the code that has access to the state runs on the actor’s queue. There are currently two holes I can think of that would prevent us from having that protection: await and escaping blocks. await could be made safe if it were changed to return to the calling queue. Maybe escaping blocks could be restricted to only calling actor methods.

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.

···

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Adding a bit more about that: I think it doesn't matter what the callee is (async vs. actor). What matters is we're calling from an actor method and the compiler should guarantee that we're running in the context of the actor's queue. Therefore I would tend to think there should be no need for manual queue hopping. The compiler should just take care of it.

Thomas

···

On 24 Aug 2017, at 22:05, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Am 24.08.2017 um 01:56 schrieb Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>>:

On Aug 23, 2017, at 4:28 PM, Marc Schlichte via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Am 23.08.2017 um 12:29 schrieb Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On 23 Aug 2017, at 11:28, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

1. What happens to the actor's queue when the body of a (non void-returning) actor method awaits away on some other actor? Does it suspend the queue to prevent other messages from being processes? It would seem to be the expected behavior but we'd also need a way to detach from the actor's queue in order to allow patterns like starting a long-running background operation and still allowing other messages to be processed (for example, calling a cancel() method). We could still do these long-running operations by passing a completion block to the method, rather than via its return value. That would clarify this goes beyond this one actor message, but we're back to the old syntax...

Maybe that's where Futures would come in handy? Just return a Future from the method so callers can await long-running operations.

If you wrap the call to a long-running operation of another actor in a `beginAsync`, I would assume that other `actor funcs` of your actor will be able to run even while
the long-running operation is pending:

actor class Caller {
  let callee = Callee()
  var state = SomeState()

  actor func foo() {
    beginAsync {
      let result = await callee.longRunningOperation()
      // do something with result and maybe state
    }
  }
  actor func bar() {
    // modify actor state
  }
}

As currently proposed, the “// do something with result and maybe state” line would likely run on Callee’s queue, not Caller’s queue. I still strongly believe that this behavior should be reconsidered.

It does, however, bring up some interesting questions about how actors interact with themselves. One of the rules laid out in Chris’s document says that “local state and non-actor methods may only be accessed by methods defined lexically on the actor or in an extension to it (whether they are marked actor or otherwise).” That means it would be allowed for the code in foo() to access state and call non-actor methods, even after the await. As proposed that would be unsafe, and since the queue is an implementation detail inaccessible to your code there wouldn’t be a straightforward way to get back on the right queue to make it safe. I presume you could call another actor method to get back on the right queue, but having to do that explicitly after every await in an actor method seems tedious and error prone.

In order to have strong safety guarantees for actors you would want to ensure that all the code that has access to the state runs on the actor’s queue. There are currently two holes I can think of that would prevent us from having that protection: await and escaping blocks. await could be made safe if it were changed to return to the calling queue. Maybe escaping blocks could be restricted to only calling actor methods.

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

So the way a non "fire and forget" actor method would work is:

- the actor's queue is in a suspended state until the method returns, this is required so that messages sent to other actor methods are not processed (they're added to the queue)
- if the method body awaits on some other code, it automatically jumps back on the actor's queue after awaiting, regardless of the queue's suspension and content
- when the method returns, the actor's queue is resumed and pending messages can be processed (if any)

···

On 24 Aug 2017, at 23:47, Adam Kemp <adam.kemp@apple.com> wrote:

On Aug 24, 2017, at 1:05 PM, Thomas via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24 Aug 2017, at 21:48, Marc Schlichte <marc.schlichte@googlemail.com <mailto:marc.schlichte@googlemail.com>> wrote:

Yes, I think it is mandatory that we continue on the callers queue after an `await ` on some actor method.

If you `await` on a non-actor-method though, you would have to changes queues manually if needed.

Any `actor` should have a `let actorQueue: DispatchQueue` property so that we can call in these cases:

```await actorQueue.asyncCoroutine()``` as mentioned in the manifesto.

Wouldn't that be really confusing though? That awaiting certain methods would bring us back to the actor's queue but awaiting others would require manual queue hopping? What if the compiler was to always generate the 'await actorQueue.asyncCoroutine()' queue hopping code after awaiting on an async/actor method?

Yes, it would be confusing. await should either always return to the same queue or never do it. Otherwise it’s even more error-prone. I see the actor feature as being just another demonstration of why solving the queue-hopping problem is important for async/await to be useful.