<div dir="ltr">That's the second time today somebody has responded to one of my messages before I posted it. Glitch in the Matrix?<div><br></div><div>I'll post a more detailed response later on but the tl;dr is that I think you're right on target. My first impression is that both of my major concerns could be solved and it would enable us to arrive at the "clean" version of the minimal example. Very nice!</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Thu, Aug 31, 2017 at 3:36 PM, Joe Groff <span dir="ltr"><<a href="mailto:jgroff@apple.com" target="_blank">jgroff@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><div class="h5"><br><div><blockquote type="cite"><div>On Aug 31, 2017, at 3:04 PM, Nathan Gray via swift-evolution <<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>> wrote:</div><br class="m_5380420149573790606Apple-interchange-newline"><div><div dir="ltr">I've been following the conversations around Chris Lattner's intriguing async/await proposal and would like to offer my own take. I feel that the proposal as written is almost perfect. My suggestions for improving it are not highly original -- I think they have all come up already -- but I'd like to present them from my own perspective.<div><br></div><div>1. Fixing "queue confusion" *must* be part of this proposal. The key bit of "magic" offered by async/await over continuation passing is that you're working in a single scope. A single scope should execute on a single queue unless the programmer explicitly requests otherwise. Queue hopping is a surprising problem in a single scope, and one that there's currently no adequate solution for.</div><div><br></div><div>Also consider error handling. In this code it's really hard to reason about what queue the catch block will execute on! And what about the defer block?</div><div><br></div><div>```</div><div><font face="monospace, monospace">startSpinner()</font></div><div><font face="monospace, monospace">defer { stopSpinner() }</font></div><div><span style="font-family:monospace,monospace">do {</span><br></div><div><font face="monospace, monospace"> try await doSomeWorkOnSomeQ()</font></div><div><font face="monospace, monospace"> try await doSomeMoreWorkOnSomeOtherQ()</font></div><div><font face="monospace, monospace">} catch {</font></div><div><font face="monospace, monospace"> // Where am I?</font></div><div><font face="monospace, monospace">}</font></div><div>```</div><div><br></div><div>Please, please, PLEASE don't force us to litter our business logic with gobs of explicit queue-hopping code!</div><div><br></div><div>2. The proposal should include some basic coordination mechanism. The argument against returning a Future every time `await` is called is convincing, so my suggestion is to do it from `beginAsync`. The Future returned should only be specified by protocol. The protocol can start with minimal features -- perhaps just cancellation and progress. There should be a way for programmers to specify their own, more featureful, types. (The proposal mentions the idea of returning a Bool, which is perhaps the least-featureful Future type imaginable. :-)</div><div><br></div><div>To make this a little more concrete. Let's look at how these proposals can clean up Jakob Egger's excellent example of more realistic usage of async. Here's the original "minimal" example that Jakob identified:<br></div><div><br></div><div><div>```</div><div><font face="monospace, monospace">class ImageProcessingTask {</font></div><div><font face="monospace, monospace"> var cancelled = false</font></div><div><font face="monospace, monospace"> func process() async -> Image? { … }</font></div><div><font face="monospace, monospace">}</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">var currentTask: ImageProcessingTask?</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:<wbr>AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancelled = true</font></div><div><font face="monospace, monospace"> let task = ImageProcessingTask()</font></div><div><font face="monospace, monospace"> currentTask = task</font></div><div><font face="monospace, monospace"> beginAsync {</font></div><div><font face="monospace, monospace"> guard let image = await task.process() else { return }</font></div><div><font face="monospace, monospace"> DispatchQueue.main.async {</font></div><div><font face="monospace, monospace"> guard task.cancelled == false else { return }</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div></div><div>```</div><div><div><br></div><div>Here's how it could look with the changes I'm proposing. I will start with the bare code to demonstrate the clarity, then add some comments to explain some subtle points:</div><div><br></div><div>```</div><div><div><font face="monospace, monospace">func processImage() async -> Image? { ... }</font></div><div><font face="monospace, monospace">weak var currentTask: Future<Void>?</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:<wbr>AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancel()</font></div><div><font face="monospace, monospace"> currentTask = beginAsync { task in</font></div><div><font face="monospace, monospace"> guard let image = await processImage() else { return }</font></div><div><font face="monospace, monospace"> if !task.isCancelled {</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div></div><div>```</div><div><br></div><div>IMHO this is a very clean expression of the intended behavior. Each line is business logic -- there is no extraneous code in service of the execution model. Here's the annotated version:</div><div><br></div><div><div>```</div><div><font face="monospace, monospace">func processImage() async -> Image? { ... }</font></div><div><font face="monospace, monospace">// If ARC can be taught to keep an executing future alive we can </font></div><div><font face="monospace, monospace">// make this weak and not worry about cleanup. See below.</font></div><div><span style="font-family:monospace,monospace">weak var currentTask: Future<Void>?</span><br></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">@IBAction func buttonDidClick(sender:<wbr>AnyObject) {</font></div><div><font face="monospace, monospace"> currentTask?.cancel()</font></div><div><font face="monospace, monospace"> // `beginAsync` creates a future, returns it, and passes it into</font></div><div><font face="monospace, monospace"> // the closure's body.</font><span style="font-family:monospace,monospace"> Can use beginAsync(MyCustomFutureType)<wbr> </span></div><div><span style="font-family:monospace,monospace"> // for your own future type</span></div><div><font face="monospace, monospace"> currentTask = beginAsync { task in</font></div><div><font face="monospace, monospace"> /* You can imagine there's implicitly code like this here</font></div><div><font face="monospace, monospace"> task.retain(); defer { task.release() }</font></div><div><font face="monospace, monospace"> */</font></div><div><font face="monospace, monospace"> guard let image = await processImage() else { return }</font></div><div><font face="monospace, monospace"> // Guaranteed back on the main queue here w/o boilerplate.</font></div><div><font face="monospace, monospace"> if !task.isCancelled {</font></div><div><font face="monospace, monospace"> imageView.image = image</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace">}</font></div><div>```</div></div><div><br></div><div><div>Note that if `beginAsync` is spelled `async` (which I do like) this idea dovetails nicely with the proposal to use `async` in place of `await` to get "future-lite" behavior. My personal belief is that there's no such thing as "future-lite" and the ideas are largely equivalent.</div><div><br></div></div><div>This got pretty long so thanks for reading! I look forward to hearing your comments.</div></div></div></div></blockquote><br></div></div></div><div>You're not the only one to make these observations. What do you think about the modifications I just posed here?</div><div><br></div><div><a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170828/039349.html" target="_blank">https://lists.swift.org/<wbr>pipermail/swift-evolution/<wbr>Week-of-Mon-20170828/039349.<wbr>html</a></div><span class="HOEnZb"><font color="#888888"><div><br></div><div>-Joe</div><br></font></span></div></blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature">Functional Programmer, iOS Developer, Surfs Poorly<br><a href="http://twitter.com/n8gray" target="_blank">http://twitter.com/n8gray</a></div>
</div>