News for hackers. Stuff that matters.
Re:What Color Is Your Function? (2015) by philwelch on Tuesday May 19, 2020 @03:25AM (#23232202)
Except in Go you could just as easily call:

  foo := bar()
and if bar() is a function that does something annoying or time-consuming before finishing its work and returning the value, it just behaves like a normal, synchronous function call. “Await” is, as TFA explains, syntactic sugar for slapping an async function into behaving like a normal synchronous function call. In Go there is no reason you can’t just write your function calls synchronously in the first place.
Re:What Color Is Your Function? (2015) by jayd16 on Tuesday May 19, 2020 @03:49AM (#23232369)
You mean if you rewrite bar so it didn't use a channel?

That's syntactically different though, isn't it? Would the Go runtime lock you to that specific thread and block it or sleep that goroutine and move it to another thread when its no longer sleeping? Maybe in Go there's no difference in those concepts but in many languages certain threads are special. UI threads are often used for event serializiation but beyond that you might have a GL context on a specific thread.

How does Go manage that? How do you protect yourself from the runtime splitting your single thread work at a point you didn't intend? You probably have to make sure you only have a main goroutine that runs in a threaded way (as they do) and pass messages to it.

I can't really find an idiomatic Go UI example so I don't know what the answer is.

Re:What Color Is Your Function? (2015) by MHordecki on Tuesday May 19, 2020 @07:37AM (#23233717)
> Would the Go runtime lock you to that specific thread and block it or sleep that goroutine and move it to another thread when its no longer sleeping?

By default, the Go runtime does not guarantee any affinity between Go-level threads (goroutines) and OS threads. There's a way to forcibly pin a goroutine to a specific thread[1], but it limits your concurrency.

The Go approach does indeed cause problems wrt. OS-thread-affine state. It's a tradeoff. For RPC-oriented network services, the Go niche, OS thread state is very rare.

[1]: https://golang.org/pkg/runtime/#LockOSThread

Re:What Color Is Your Function? (2015) by philwelch on Tuesday May 19, 2020 @12:11PM (#23236488)
Worth pointing out that invoking “await” in some languages also permits a context switch.
Re:What Color Is Your Function? (2015) by tsimionescu on Tuesday May 19, 2020 @09:59AM (#23234926)
> In Go there is no reason you can’t just write your function calls synchronously in the first place.

There are exactly the same reasons in Go to want asynchronous calls as there are in C# or Java or C++, except that performance of multi-threaded code (which is the semantics of goroutines) is much nicer in Go. Sure, channels can sometimes be a nice alternative to locking, if you can afford all of the copying.

But Go is stuck with only multi-threaded code + synchronous calls + locking/channels, whereas in C# or Java or even C++ you can chose between using that OR asynchronous code with futures.

Re:What Color Is Your Function? (2015) by philwelch on Tuesday May 19, 2020 @12:07PM (#23236422)
> There are exactly the same reasons in Go to want asynchronous calls

Which isn’t the comparison I’m replying to here. Calling an asynchronous function with “await” forces it to behave synchronously. You would only do such a thing if you were operating in a framework or language with colored functions.

> in C# or Java or even C++ you can chose between using that OR asynchronous code with futures.

Java, C#, and C++ have channels?

Re:What Color Is Your Function? (2015) by tsimionescu on Tuesday May 19, 2020 @03:08PM (#23238817)
> Calling an asynchronous function with “await” forces it to behave synchronously. You would only do such a thing if you were operating in a framework or language with colored functions.

I don't really think you are right. When you await an async function, you let the async function run asynchronously, but suspend your own execution until you can receive the result from that function (an actual return value, an exception, or simply termination for void functions).

Calling a function directly forces it to run on the same execution thread as you. Calling it with await allows it to run in any thread. This is the actual advantage, and Go doesn't have any equivalent construct that is as convenient for this use case.

Also, note that a function that expects to return data through channels can't be called in a sync manner in Go or it will deadlock. So in essence there is function coloring in Go as well.

> Java, C#, and C++ have channels?

Not out of the box, but they are easy to replicate if desired, wrapping a lock in a send/receive interface (you can add a buffer as well if desired). It is probably not as efficient, but it may not be vastly different either.

Re:What Color Is Your Function? (2015) by philwelch on Tuesday May 19, 2020 @06:35PM (#23241102)
> Calling a function directly forces it to run on the same execution thread as you. Calling it with await allows it to run in any thread. This is the actual advantage, and Go doesn't have any equivalent construct that is as convenient for this use case.

OK so Thread A calls “await” on a coroutine that executes in Thread B (where B may or may not be A). Thread A is now blocked on that coroutine. What have I gained by running that coroutine in Thread B?

One potential answer is that while Thread A is blocked by “await”, it can context-switch to a different coroutine. You can effectively do similar things in Go if you want to. But doing so abandons the guarantee that Thread A will pick up where it left off as soon as Thread B is finished.

> Also, note that a function that expects to return data through channels can't be called in a sync manner in Go or it will deadlock. So in essence there is function coloring in Go as well.

Is this a popular or idiomatic interface for Go library code to the same degree it is for “async” libraries in other languages?

In isolation I find it more understandable to do channel writes as an explicit side effect than to manage futures but maybe that’s just my brain.

> Not out of the box, but they are easy to replicate if desired

I’m pretty sure you could implement futures and async/await using Go channels too if you wanted to.

Re:What Color Is Your Function? (2015) by simiones on Wednesday May 20, 2020 @08:45AM (#23246215)
> I’m pretty sure you could implement futures and async/await using Go channels too if you wanted to.

Futures, maybe (though without generics you'll be either very dynamic or write a new future for each struct, of course).

But async/await is a syntactic feature and can't be implemented in a language without macros and/or continuations. Basically `await` is a keyword which returns a future that will execute the rest of the function as written. Something like this is relatively easy to implement:

    async Task<int> Foo() { 
       int i := await Bar();
       return i + 1;
    }
You could re-write it to something like this in Go:

    func Foo() func()(int){
         var reply chan int
         go func() {
             reply <- Bar()
         }
         return func()(int) {
             i := <- reply
             return i 
         }
    }
Or something similar. Maybe you could even reduce the boilerplate, though it's already much worse than the C# verison. But this is much more difficult to re-write in Go:

    async Task<int> Foo() {
        auto sum = 0;
        foreach (auto x in someEnumerable) {
               switch(x) {
                     case 1:
                          sum += await Bar(x);
                          goto case 2;
                     case 2:
                          sum /= await Bar(x);
                          break;
                   }
        }
        return sum;
    }
Assuming you want to keep the asynchronicity, this gets much uglier to implement in terms of channels (not that this is very common code).
Re:What Color Is Your Function? (2015) by philwelch on Wednesday May 20, 2020 @12:40PM (#23248943)
I don’t really want to implement async/await in terms of channels. I would rather just use channels directly. Notice that even in your Go code it’s explicit which part executes as a separate coroutine and where the blocking communication between coroutines takes place. Async/await is more “magical”, or maybe I’m just dumb.
Re:What Color Is Your Function? (2015) by simiones on Thursday May 21, 2020 @09:47AM (#23258275)
Hmm, I think it only looks magical because I chose a relatively unfair comparison, I realize now.

To show a more realistic comparison, let's asume Bar() is not "async ready" in either Go or C#. Then, the C# code would look something like this:

    async Task<int> Foo() { 
       var task = Task.Run(() => Bar());
       int i = await task;
       return i + 1;
    }
This compares more clearly to the Go version with the same assumption:

    func Foo(ret chan int) {
        task := make(chan int, 1)
        go func() {
             task <- Bar()
        }
        i := <-task
        ret <- i + 1
    }
So the readability / cleanliness is pretty similar for simple examples. However, once you start doing more complex things, like I showed in my second example, the difference become much more pronounced. Overall, a `Task<T>` offers much more information to callers than a `chan T`, so it can usually be composed in more interesting ways, but it can also sometimes be more cumbersome to use.

And again, in both languages, there are clear demarcations between async functions and blocking functions. In C#, async functions return `Task<T>` and are usually marked `async`, while in Go async functions have one or more `chan T` arguments and don't normally return values. If you want to call an async function from a non-async function or vice-versa, you need to use special constructs (e.g. Task.Run, Task.Wait or Task.Result for C#, and `go func () {...}` and blocking channel reads/writes in Go).

Re:What Color Is Your Function? (2015) by tsimionescu on Wednesday May 20, 2020 @03:02AM (#23244082)
> OK so Thread A calls “await” on a coroutine that executes in Thread B (where B may or may not be A). Thread A is now blocked on that coroutine. What have I gained by running that coroutine in Thread B?

If the task that bar() will return is created when you first call it, then you're right, we didn't gain much. However, the task may have already been running for a long time behind the scenes, we may have done things in parallel with that run, and now that we need the result, we can block.

For example:

    myHttpClient.StartReq1()
    myHttpClient.StartReq2()
    auto Res1 = await myHttpClient.WaitReq1()
    auto res2 = await myHttpClient.WaitReq2()
> I’m pretty sure you could implement futures and async/await using Go channels too if you wanted to.

Given the lack of generics, you would get a much worse interface. Btw, here is what a non-buffering channel would look like in Java:

    class Channel<T> {
      T value;

      void publish(T value) {
        synchronized(this) {
           this.value = value;
           try {
             this.wait();
           } catch (InterruptedException e) {} 
        }
      }

      T consume() {
        synchronized(this) {
          this.notify();
          return this.value;
        }
      }
     }
Re:What Color Is Your Function? (2015) by philwelch on Wednesday May 20, 2020 @12:02PM (#23248462)
> If the task that bar() will return is created when you first call it, then you're right, we didn't gain much. However, the task may have already been running for a long time behind the scenes, we may have done things in parallel with that run, and now that we need the result, we can block.

So in Go that’s just a blocking channel receive.

Re:What Color Is Your Function? (2015) by jayd16 on Wednesday May 20, 2020 @05:20PM (#23251935)
Not exactly. Async allows for N tasks of linear code execution on the same thread at the cost of managing cooperative yields.

Go and channels allow you to block many threads with little overhead and little syntax but at the cost of not being able to easily target a single thread without manual orchestration through channels.

Consider a scatter/gather algorithm. N async tasks, N coroutines. Start N from the main thread and block until all complete. Simple right?

Its not!

Imagine there are GPU commands that must be grouped or even logs where you want some of the subroutine's logs to be grouped.

With async/await you have the verbosity to synchronize tasks to thread contexts such that you know chunks will not be run in parallel. Its easy to control when execution leaves the current thread. It can ensure that it both does or does not yield execution. You can easily switch between synchronized main thread execution and parallel chunks without any top down design.

With goroutines you most likely would write the ordered results to a channel that was passed around. I'm not sure if channels support N inserts in an atomic fashion out of the box but if not it must be a channel of arrays or maybe some kind of control channel as well. Hopefully you have access to every piece of code you need to synchronize. This all assumes you can get away with a single main goroutine. If you need a single special OS thread for interop I'm sure its more complex. Its not just a blocking channel.

Essentially the two paradigms can do everything but they both seem to excel in certain cases.

Re:What Color Is Your Function? (2015) by simiones on Thursday May 21, 2020 @09:53AM (#23258348)
Sure, it is, but now you're moving the goal posts. The initial assertion was that `var x = await Bar()` is equivalent to `x := Bar()` in Go, and I was explaining that it is not.

Yes, `await Bar()` is equivalent to ` <- ch`, but only if there is some goroutine that actually writes something to the channel. So there is no getting around the fact that there are colored functions to the same extent in Go as in C#/Java, in regards to asynchronicity.

Re:What Color Is Your Function? (2015) by philwelch on Thursday May 21, 2020 @12:59PM (#23261039)
What’s unclear and magical to me is the notion that Bar() is returning the result of a computation that was potentially started a long time ago. In reality, “await Bar()” could be a clumsy attempt to force a needlessly async function to behave synchronously, or it could be simply receiving a result from some other task that’s been running behind the scenes. So you’re shifting the goalposts a little bit, too—awaiting Bar() is only equivalent to a channel receive if there is some other task that actually started the work.

And I maintain that Go does not have colored functions. Colored functions are when you hijack function call semantics to do asynchronous programming. Go doesn’t do this; the asynchronous behavior has to be done explicitly. Even though it’s possible, it isn’t idiomatic for a function to return a channel that you have to try and receive later, the way async functions have to be awaited before you actually get the return value.

Re:What Color Is Your Function? (2015) by simiones on Friday May 22, 2020 @07:49AM (#23271088)
> So you’re shifting the goalposts a little bit, too—awaiting Bar() is only equivalent to a channel receive if there is some other task that actually started the work.

The most correct way of looking at this is that async fucntions return a channel that you can read data from, and await is exactly like a channel read. The magic in async/await comes in the function calling 'await', which is suspended and scheduled to continue later. So, just like in Go, you could create a function that needlessly uses channels but is otherwise synchronous, or you can have a function that uses channels because it actually is doing something in the background.

> Colored functions are when you hijack function call semantics to do asynchronous programming. Go doesn’t do this; the asynchronous behavior has to be done explicitly.

Colored functions are a very general concept. They refer to families of functions which perform the same computation but that need to be written differently for syntactic reasons. Famous examples that have nothing to do with async include C++ const-ness, where you have to write a const and a non-const version of a function to to be able to offer the same functionality for this and for const this.

Go has this with functions which return data versus functions that push data on a channel. If you want your function to be callable in a sync manner, it must return the data directly. If you want it to be callable in an async manner, it must take a channel and push its result on that channel. You can't launch a sync function as a new goroutine and get the result back. You can't call an async function as a regular function and read the result back. So, they are two colors of functions in Go.

More concretely, say you are writing an HTTP client lib. If you want to allow your users to start multiple requests in parallel in a nice fashion, you must write your request function to take a channel and push its response there. If you want to make it easy for your users to just call your function in a sync manner, you must write a function that returns the response as its return value.

Of course, you can implement one in terms of the other, as demonstrated earlier. But you still need to add that wrapping, so your functions have a color.

Re:What Color Is Your Function? (2015) by jayd16 on Tuesday May 19, 2020 @03:43PM (#23239260)
>Java, C#, and C++ have channels?

Yeah they do but without green threads the implications are a bit different. You can use channels and OS threads though most code bases don't.