On 29.08.2017 19:02, Wallacy via swift-evolution wrote:
In this example i think we lose clarity, just looking for the code we cant know if
this two line will run on parallel or not!
Also, image.get blocks the thread, in this case we need the await anyway! And `async`
can throws too... So the error handler can be pretty similar.
let image= asyncpreprocessImage(downloadImage()) // These first two lines run in
parallel and I can "see" the async keyword.
let text= asynctranslate(downloadText())
await render(image: image ?? defaultImage,text: text ?? defaultText) // No
blocking!
FWIW: I'm following the whole discussion from the start, and do support the opinion
that async/await is much clear solution that proposed Futures, especially for beginners.
We need a low-level building blocks which can be used to implement Futures/Promises
in libraries.
Also I really like the idea of 'async' on the caller side to have code running in
parallel.
The 'async' version of func declaration is clearly saying what type it *want* to
return, and 'async' modifier just saying *how* it will/can return that type('Image'
in examples). So on both sides, on declaration and on caller side, we are clear what
types we are working with.
Future<Type> - is mixing of what is returning and how this will be returned. Code is
saying that we preprocessesImage, but actually we have Future<Image> type, no
'markers' of asynchronous code.
Also, I wonder(if I missed that in proposal/discussion, please let me know), if I
have async function like
func foo() async -> Type {}
, may I want to call it synchronously? If so, what would be a solution here? I can
think about something like 'sync' modifier on caller side:
let x = sync foo() // calling asynchronous function synchronously
I believe that is what Future.get is doing, no?
let future = ...
future.get() // blocks the execution, waits for the result.
Probably it is reasonable to allow just call foo() to get blocking result, just like
any other 'simple' blocking funcs that we call, but this can lead to unexpected
behavior as user can expect async execution.
With Futures, it seems like we can't "just" call such function and need to call
.get() later:
let future = someFuncReturnsFuture() // already returns Future<Type> type
Vladimir.
Like i said before! Today's, the proposal only lack two things over the
`Future`....
Parallel computing: Can be implemented by a third party library or a personal one,
but i don't think this is a good approach to the first version.
Coordination: This we can wait! And why? Because coordination, can be made in
different ways, maybe is more suitable to a standard library class/function, not a
language level resource.
Also, coordination cant be applied to all variants of the runtimes in the same way!
async/await as language level works just as well with GCD as with pthreads or
another API. And coordination is a compromise that we can make after that one.
Em ter, 29 de ago de 2017 às 05:23, Howard Lovatt via swift-evolution
<[email protected] <mailto:[email protected]>> escreveu:
@David,
Using the `Future` library based on GCD that I have previously posted your
example would be:
let image= preprocessImage(downloadImage()) // These first two lines run
in parallel
let text= translate(downloadText())
render(image: image.get ?? defaultImage,text: text.get ?? defaultText)
The main difference, and I would argue an improvement, is that the `Future`
version handles errors.
So what advantage does async/await have over a `Future` library we can
write today?
-- Howard.
On 29 August 2017 at 15:28, David Hart via swift-evolution
<[email protected] <mailto:[email protected]>> wrote:
On 29 Aug 2017, at 02:22, Xiaodi Wu via swift-evolution
<[email protected] <mailto:[email protected]>> wrote:
On Mon, Aug 28, 2017 at 16:10 Adam Kemp via swift-evolution
<[email protected] <mailto:[email protected]>> wrote:
I know what the proposal said. I’m making a case that there is
value in
doing it differently.
The composability of futures is valuable. Mixing and matching
async/await with futures is also valuable. The queue-returning
behavior
that you can get from futures is also valuable, and building
async/await on top of futures means async/await can get that for
free.
Why couldn't you mix and match async/await and futures and get the
queue-return behavior of futures if futures are built on top of
async/await
instead off the other way around?
We could, but the syntax is much worse. Contrast:
*async/await built on top of Futures*
*
*
let image= preprocessImage(downloadImage())
let text= translate(downloadText())
awaitrender(image: image,text: text)
*Futures built on top of async/await*
*
*
let image= Future(downloadImage).then({preprocessImage($0) })
let text= Future(downloadText).then({translate($0) })
awaitrender(image: image.get(),text: text.get())
Maybe you don’t value those things, which is fine. But I do, and
maybe
other people do too. That’s why we’re having a discussion about it.
It can also be valuable having a minimal implementation, but we
have to
acknowledge that it comes with a downside as well. The problem with
doing a minimal implementation is that you can be stuck with the
consequences for a long time. I want to make sure that we’re not
stuck
with the consequences of a minimal implementation that doesn’t
adequately address the problems that async/await should be
addressing.
I’d hate for Swift to get an async/await that is so weak that it
has to
be augmented by tedious boilerplate code before it’s useful.
On Aug 28, 2017, at 1:54 PM, Wallacy <[email protected]
<mailto:[email protected]>> wrote:
We don't need to this now!
Again: (Using proposal words)
"It is important to understand that this is proposing compiler
support
that is completely concurrency runtime-agnostic. This proposal does
not include a new runtime model (like "actors") - it works just as
well with GCD as with pthreads or another API. Furthermore, unlike
designs in other languages, it is independent of specific
coordination
mechanisms, such as futures or channels, allowing these to be built
as
library feature"
and
"This proposal does not formally propose a |Future| type, or any
other
coordination abstractions. There are many rational designs for
futures, and a lot of experience working with them. On the other
hand,
there are also completely different coordination primitives that can
be used with this coroutine design, and incorporating them into this
proposal only makes it larger."
and
We focus on task-based concurrency abstractions commonly encountered
in client and server applications, particularly those that are
highly
event driven (e.g. responding to UI events or requests from
clients).
This does not attempt to be a comprehensive survey of all possible
options, nor does it attempt to solve all possible problems in the
space of concurrency. Instead, it outlines a single coherent design
thread that can be built over the span of years to incrementally
drive
Swift to further greatness.
and
This proposal has been kept intentionally minimal, but there are
many
possible ways to expand this in the future.
....
The point is: No Future type is indeed proposed yet!
The proposal try to include de "minimum" required to implement a
basic
async/await to solve the problem created by the GCD! (Pyramid of
doom)
The question is: How do you do the same using dispatch_async ?
dispatch_async also does not return nothing to do what you are
intentend do do!
Algo, by Swift 5 manifesto, there's no compromise to make a
"complete"
concurrency model by this time!
My intention is only make parity to dispatch_async, but also make
the
ground free to make more complex implementation like Futures in
another round on top of this one.
This 'async T' can be a real type in the future? Maybe will... But
doesn't matter now! Now we only need to is some kind of type which
need to be unwrapped using await before use. Maybe this
intermediary/virtual type can be a real thing and gain some
abilities
at some point! Maybe a full Future type, why not?
Em seg, 28 de ago de 2017 às 17:33, Adam Kemp <[email protected]
<mailto:[email protected]>> escreveu:
How would these anonymous types get composed? If I wanted to
implement a function that takes a collection of futures and wait
on it, how would I do that? That is, how would I implement the
equivalent of C#’s Task.WhenAll and Task.WhenAny methods?
More generally, how do you pass one of these typeless futures to
some other function so that we can do the waiting there?
On Aug 28, 2017, at 1:23 PM, Wallacy <[email protected]
<mailto:[email protected]>> wrote:
And that's why I (and others) are suggesting:
func processImageData1a() async -> Image {
let dataResource = async loadWebResource("dataprofile.txt")
//
No future type here... Just another way to call dispatch_async
under the hood.
let imageResource = async loadWebResource("imagedata.dat")
// ... other stuff can go here to cover load latency...
let imageTmp = await decodeImage(dataResource,
imageResource) // Compiles force await call here...
let imageResult = await dewarpAndCleanupImage(imageTmp)
return imageResult
}
And now we gain all advantages of async/await again without to
handle with one more type.
Em seg, 28 de ago de 2017 às 17:07, Adam Kemp via
swift-evolution
<[email protected] <mailto:[email protected]>>
escreveu:
I think the biggest tradeoff is clearer when you look at the
examples from the proposal where futures are built on top of
async/await:
func processImageData1a() async -> Image {
let dataResource = Future { await
loadWebResource("dataprofile.txt") }
let imageResource = Future { await
loadWebResource("imagedata.dat") }
// ... other stuff can go here to cover load
latency...
let imageTmp = await
decodeImage(dataResource.get(),
imageResource.get())
let imageResult = await
dewarpAndCleanupImage(imageTmp)
return imageResult
}
With this approach you have to wrap each call site to create
a future. Compare to this:
func processImageData1a() -> Future<Image> {
let dataResourceFuture =
loadWebResource("dataprofile.txt”);
let imageResourceFuture =
loadWebResource("imagedata.dat”);
// ... other stuff can go here to cover load
latency...
let imageTmp = await decodeImage(await
dataResourceFuture, await imageResourceFuture)
let imageResult = await
dewarpAndCleanupImage(imageTmp)
return imageResult
}
Here, not only are the explicit wrappers gone, but this
function itself can be used with either await or as a
future.
You get both options with one implementation.
As I’ve mentioned before, C#’s implementation is not tied to
any one particular futures implementation. The Task type is
commonly used, but async/await does not directly depend on
Task. Instead it works with any return type that meets
certain requirements (detailed here:
https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything/).
Swift could do this using a protocol, which can be
retroactively applied using an extension.
Obviously for this to be useful we would need some kind of
existing future implementation, but at least we wouldn’t be
tied to any particular one. That would mean library
maintainers who have already been using their own futures
implementations could quickly adopt async/await in their
code
without having to rewrite their futures library or throw
wrappers around every usage of async/await. They could just
adopt a protocol (using an extension, even) and get
async/await support for free.
The downside is that this feature would be specific to the
async/await use case rather than a generic coroutine
implementation (i.e., there would have to be a separate
compiler transform for yield return). It’s not clear to me
why it should be a goal to have just one generic coroutine
feature. The real-world usages of async/await and yield
return are different enough that I’m not convinced we could
have a single compiler feature that meets the needs of both
cleanly.
On Aug 27, 2017, at 7:35 PM, Florent Vilmart
<[email protected] <mailto:[email protected]>>
wrote:
Adam, you’re completely right, languages as c# and JS have
been through the path before, (callback, Promises ,
async/await) I believe Chris’s goal it to avoid building a
promise implementation and go straight to a coroutines
model, which is more deeply integrated with the compiler. I
don’t see a particular trade off, pursuing that route, and
the main benefit is that coroutines can power any
asynchronous metaphor (Signals, Streams, Futures, Promises
etc...) which is not true of Futures so i would tend to
think that for the long run, and to maximize usability,
async/await/yield would probably be the way to go.
On Aug 27, 2017, 22:22 -0400, Adam Kemp <[email protected]
<mailto:[email protected]>>, wrote:
As has been explained, futures can be built on top of
async/await (or the other way around). You can have the
best of both worlds. We are not losing anything by having
this feature. It would be a huge improvement to have this
as an option.
However, using futures correctly requires more nested
closures than you have shown in your examples to avoid
blocking any threads. That's why you're not seeing the
advantage to async/await. You're comparing examples that
have very different behaviors.
That said, I have also expressed my opinion that it is
better to build async/await on top of futures rather than
the other way around. I believe it is more powerful and
cleaner to make async/await work with any arbitrary future
type (via a protocol). The alternative (building futures on
top of async/await) requires more code when the two are
mixed. I very much prefer how it's done in C#, where you
can freely mix the two models without having to resort to
ad-hoc wrappers, and you can use async/await with any
futures implementation you might already be using.
I really think we should be having more discussion about
the tradeoffs between those two approaches, and I'm
concerned that some of the opinions about how C# does it
are not based on a clear and accurate understanding of how
it actually works in that language.
--
Adam Kemp
On Aug 27, 2017, at 6:02 PM, Howard Lovatt
<[email protected] <mailto:[email protected]>>
wrote:
The async/await is very similar to the proposed Future (as
I posed earlier) with regard to completion-handler code,
they both re-write the imported completion-handler
function using a closure, the relevant sentence from the
Async Proposal is:
"Under the hood, the compiler rewrites this code using
nested closures ..."
Unlike the proposed future code the async code is not
naturally parallel, in the running example the following
lines from the async code are run in series, i.e. await
blocks:
let dataResource=
awaitloadWebResource("dataprofile.txt")
let imageResource=
awaitloadWebResource("imagedata.dat")
The equivalent lines using the proposed Future:
let dataResource= loadWebResource("dataprofile.txt")
let imageResource= loadWebResource("imagedata.dat")
Run in parallel and therefore are potentially faster
assuming that resources, like cores and IO, are available.
Therefore you would be better using a Future than an
async, so why provide an async unless you can make a
convincing argument that it allows you to write a better
future?
-- Howard.
On 28 August 2017 at 09:59, Adam Kemp <[email protected]
<mailto:[email protected]>> wrote:
This example still has nested closures (to create a
Future), and still relies on a synchronous get method
that will block a thread. Async/await does not require
blocking any threads.
I’m definitely a fan of futures, but this example
isn’t even a good example of using futures. If you’re
using a synchronous get method then you’re not using
futures properly. They’re supposed to make it easy to
avoid writing blocking code. This example just does
the blocking call on some other thread.
Doing it properly would show the benefits of
async/await because it would require more nesting and
more complex error handling. By simplifying the code
you’ve made a comparison between proper asynchronous
code (with async/await) and improper asynchronous code
(your example).
That tendency to want to just block a thread to make
it easier is exactly why async/await is so valuable.
You get simple code while still doing it correctly.
--
Adam Kemp
On Aug 27, 2017, at 4:00 PM, Howard Lovatt via
swift-evolution <[email protected]
<mailto:[email protected]>> wrote:
The running example used in the white paper coded
using a Future is:
func processImageData1() -> Future<Image> {
return AsynchronousFuture { _ -> Image in
let dataResource =
loadWebResource("dataprofile.txt") // dataResource
and imageResource run in parallel.
let imageResource =
loadWebResource("imagedata.dat")
let imageTmp =
decodeImage(dataResource.get ?? Resource(path:
"Default data resource or prompt user"),
imageResource.get ?? Resource(path: "Default image
resource or prompt user"))
let imageResult =
dewarpAndCleanupImage(imageTmp.get ??
Image(dataPath: "Default image or prompt user",
imagePath: "Default image or prompt user"))
return imageResult.get ?? Image(dataPath:
"Default image or prompt user", imagePath: "Default
image or prompt user")
}
}
This also avoids the pyramid of doom; the pyramid is
avoided by converting continuation-handlers into
either a sync or future, i.e. it is the importer that
eliminates the nesting by translating the code
automatically.
This example using Future also demonstrates three
advantages of Future: they are naturally parallel
(dataResource and imageResource lines run in
parallel), they timeout automatically (get returns
nil if the Future has taken too long), and if there
is a failure (for any reason including timeout) it
provides a method of either detecting the failure or
providing a default (get returns nil on failure).
There are a three of other advantages a Future has
that this example doesn’t show: control over which
thread the Future runs on, Futures can be cancelled,
and debugging information is available.
You could imagine `async` as a syntax sugar for
Future, e.g. the above Future example could be:
func processImageData1() async -> Image {
let dataResource =
loadWebResource("dataprofile.txt") // dataResource
and imageResource run in parallel.
let imageResource = loadWebResource("imagedata.dat")
let imageTmp = decodeImage(dataResource.get
?? Resource(path: "Default data resource or prompt
user"), imageResource.get ?? Resource(path: "Default
image resource or prompt user"))
let imageResult =
dewarpAndCleanupImage(imageTmp.get ??
Image(dataPath: "Default image or prompt user",
imagePath: "Default image or prompt user"))
return imageResult.get ?? Image(dataPath:
"Default image or prompt user", imagePath: "Default
image or prompt user")
}
Since an async is sugar for Future the async runs as
soon as it is created (as soon as the underlying
Future is created) and get returns an optional (also
cancel and status would be still be present). Then if
you want control over threads and timeout they could
be arguments to async:
func processImageData1() async(queue:
DispatchQueue.main, timeout: .seconds(5)) -> Image {
... }
On Sat, 26 Aug 2017 at 11:00 pm, Florent Vilmart
<[email protected]
<mailto:[email protected]>> wrote:
Howard, with async / await, the code is flat and
you don’t have to unowned/weak self to prevent
hideous cycles in the callbacks.
Futures can’t do that
On Aug 26, 2017, 04:37 -0400, Goffredo Marocchi
via swift-evolution <[email protected]
<mailto:[email protected]>>, wrote:
With both he now built in promises in Node8 as
well as libraries like Bluebird there was ample
time to evaluate them and convert/auto convert
at times libraries that loved callback pyramids
of doom when the flow grows complex into promise
based chains. Converting to Promises seems
magical for the simple case, but can quickly
descend in hard to follow flows and hard to
debug errors when you move to non trivial multi
path scenarios. JS is now solving it with their
implementation of async/await, but the point is
that without the full picture any single
solution would break horribly in real life
scenarios.
Sent from my iPhone
On 26 Aug 2017, at 06:27, Howard Lovatt via
swift-evolution <[email protected]
<mailto:[email protected]>> wrote:
My argument goes like this:
1. You don't need async/await to write a
powerful future type; you can use the
underlying threads just as well, i.e. future
with async/await is no better than future without.
2. Since future is more powerful, thread
control, cancel, and timeout, people should be
encouraged to use this; instead because
async/await are language features they will be
presumed, incorrectly, to be the best way,
consequently people will get into trouble with
deadlocks because they don't have control.
3. async/await will require some engineering
work and will at best make a mild syntax
improvement and at worst lead to deadlocks,
therefore they just don't carry their weight in
terms of useful additions to Swift.
Therefore, save some engineering effort and
just provide a future library.
To turn the question round another way, in two
forms:
1. What can async/wait do that a future can't?
2. How will future be improved if async/await
is added?
-- Howard.
On 26 August 2017 at 02:23, Joe Groff
<[email protected] <mailto:[email protected]>> wrote:
On Aug 25, 2017, at 12:34 AM, Howard
Lovatt <[email protected]
<mailto:[email protected]>> wrote:
In particular a future that is cancellable
is more powerful that the proposed
async/await.
It's not more powerful; the features are to
some degree disjoint. You can build a
Future abstraction and then use async/await
to sugar code that threads computation
through futures. Getting back to Jakob's
example, someone (maybe the Clang importer,
maybe Apple's framework developers in an
overlay) will still need to build
infrastructure on top of IBActions and
other currently ad-hoc signalling
mechanisms to integrate them into a more
expressive coordination framework.
-Joe
_______________________________________________
swift-evolution mailing list
[email protected]
<mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
--
-- Howard.
_______________________________________________
swift-evolution mailing list
[email protected]
<mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution