<div dir="ltr">That&#39;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&#39;ll post a more detailed response later on but the tl;dr is that I think you&#39;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 &quot;clean&quot; 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">&lt;<a href="mailto:jgroff@apple.com" target="_blank">jgroff@apple.com</a>&gt;</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 &lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt; wrote:</div><br class="m_5380420149573790606Apple-interchange-newline"><div><div dir="ltr">I&#39;ve been following the conversations around Chris Lattner&#39;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&#39;d like to present them from my own perspective.<div><br></div><div>1. Fixing &quot;queue confusion&quot; *must* be part of this proposal.  The key bit of &quot;magic&quot; offered by async/await over continuation passing is that you&#39;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&#39;s currently no adequate solution for.</div><div><br></div><div>Also consider error handling.  In this code it&#39;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&#39;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&#39;s look at how these proposals can clean up Jakob Egger&#39;s excellent example of more realistic usage of async.  Here&#39;s the original &quot;minimal&quot; 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 -&gt; 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&#39;s how it could look with the changes I&#39;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 -&gt; Image? { ... }</font></div><div><font face="monospace, monospace">weak var currentTask: Future&lt;Void&gt;?</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&#39;s the annotated version:</div><div><br></div><div><div>```</div><div><font face="monospace, monospace">func processImage() async -&gt; 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&lt;Void&gt;?</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&#39;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&#39;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 &quot;future-lite&quot; behavior.  My personal belief is that there&#39;s no such thing as &quot;future-lite&quot; 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&#39;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>