aboutsummaryrefslogtreecommitdiff
path: root/docs/ASYNC.md
blob: 87c602d620b680957b0cbde759479d6bc0cb11db (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Asynchronous Programming

Puck has [colourless](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) async/await, heavily inspired by [Zig's implementation](https://kristoff.it/blog/zig-colorblind-async-await/).

```puck
pub func fetch(url: str): str = ...

let a: Future[T] = async fetch_html()
let b: T = a.await
let c: T = await async fetch_html()
```

Puck's async implementation relies heavily on its metaprogramming system.

The `async` macro will wrap a call returning `T` in a `Future[T]` and compute it asynchronously. The `await` function takes in a `Future[T]` and will block until it returns a value (or error). The `Future[T]` type is opaque, containing internal information useful for the `async` and `await` routines.

```puck
pub macro async(self): Future[T] =
  ... todo ...
```

```puck
pub func await[T](self: Future[T]): T =
  while not self.ready:
    block
  self.value! # apply callbacks?
```

This implementation differs from standard async/await implementations quite a bit.
In particular, this means there is no concept of an "async function" - any block of computation that resolves to a value can be made asynchronous. This allows for "anonymous" async functions, among other things.

This (packaging up blocks of code to suspend and resume arbitrarily) is *hard*, and requires particular portable intermediate structures out of the compiler. Luckily, Zig is doing all of the R&D here. Some design decisions to consider revolve around *APIs*. The Linux kernel interface (among other things) provides both synchronous and asynchronous versions of its API, and fast code will use one or the other, depending if it is in an async context. Zig works around this by way of a known global constant that low-level functions read at compile time to determine whether to operate on synchronous APIs or asynchronous APIs. This is... not great. But what's better?

<!-- Asynchronous programming is hard to design and hard to use. Even Rust doesn't do a great job. It *shouldn't* need built-in language support - we should be able to encode it as a type and provide any special syntax via macros. Note that async is not just threading! threading is solved well by Rust's rayon and Go's (blugh) goroutines. -->

## Threading

It should be noted that async is *not* the same as threading, *nor* is it solely useful in the presence of threads...

How threads work deserves somewhat of a mention...

References:
- [What color is your function?](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)
- [What is Zig's "colorblind" async/await?](https://kristoff.it/blog/zig-colorblind-async-await/)
- [Zig Learn: Async](https://ziglearn.org/chapter-5/)
- [Rust async is colored and that's not a big deal](https://morestina.net/blog/1686/rust-async-is-colored)
- [Why is there no need for async/await in Elixir?](https://old.reddit.com/r/elixir/np688d/)
- [Async/await on Wikipedia](https://en.wikipedia.org/wiki/Async/await)
- [nim-chronos](https://github.com/status-im/nim-chronos)
- [nim-cps](https://github.com/nim-works/cps)
- [tokio](https://tokio.rs/tokio/tutorial)
- [Zig-style async/await for Nim](https://forum.nim-lang.org/t/7347)

Is async worth having separate from effect handlers? I think so...