Live data from Hacker News

What Color Is Your Function? (2015)

journal.stuffwithstuff.com

71–80 of 90 posts

Re: What Color Is Your Function? (2015)

#71

Earlier quoted context omitted.

You're just using the channel as a future. None of this is really problematic at the top level view of things. But when you need to compose libraries or applications that make use of these things - yes, even channels in Go - you can start running into problems. Especially if you don't actually control the process you're running in. This is why promises and async/await really exist. E.g. if you have code you need to f…

I don’t see how futures can be more flexible than go routines. Could you explain that more? And why couldn’t you just spawn a goroutine to avoid blocking your main thread?

The point of async/await is to make code that exits and returns to some kind of execution context - without blocking either context - read like plain, synchronous code.

A simple example of where this might be useful is in UI code. Every UI framework I've worked in has a single UI thread, where all UI changes must go - e.g. adding a button, adding text to the UI, etc. At the same time, you don't want to block the UI thread, because that will cause the UI to become unresponsive.

A common thing you might do in a UI is: on button click, make an API call, then update the UI with that data.

The on button click will be called from the UI thread. You don't want to make the API call there though because that would block the UI thread. So you spin it off into a worker thread. But after that worker finishes, you need to regain control of the UI thread so you can update the UI.

The way you traditionally do this is you have some kind of main loop which is running the UI thread, which has (among other things) some kind of queue of functions it should call (I assume it would be a channel in Go). So your worker thread will hold onto a reference to this queue. When it finishes, it pushes a function call onto the queue. The main UI loop occasionally checks this queue and calls all functions in it. So eventually it gets called, allowing you to update the UI in the UI thread.

This can be fine for simple scenarios. But if you have deeply nested or complex interactions between multiple threads, the code can get confusing and turn into "callback hell". This is the problem that was ran into in early versions of nodejs. This is the problem async/await was intended to solve. It's a bigger problem in nodejs due to its design, but it can still be useful in other languages that don't share that design depending upon the application you're writing.

Re: What Color Is Your Function? (2015)

#72
post #67
post #65

I've been thinking why exactly async-await was chosen at the function level, and not the caller level. I mean for languages that have event loops at their core, why isn't every function `async` by default? Let the caller decide how it wants to use the function. `async` functions don't wait for a result of a `Future` unless `await` is used. Instead of putting all that effort into putting `await` everywhere, why not in…

I have thinking something similar but with generators: //A async candidate fun read_lines(file): for line in file: yield line } let lines = read_lines("hello.txt").await //turn async I wonder why something like this is not used. Exist a bad interaction that could arise from this? Maybe about how nest stuff? fun uppercase_lines(file): for line in read_lines(file): yield line let lines = uppercase_lines("hello.txt").aw…

How do you plan on making this concurrent? Generators can defer computation for later, but that doesn't magically make them async.

Re: What Color Is Your Function? (2015)

#73
post #65

I've been thinking why exactly async-await was chosen at the function level, and not the caller level. I mean for languages that have event loops at their core, why isn't every function `async` by default? Let the caller decide how it wants to use the function. `async` functions don't wait for a result of a `Future` unless `await` is used. Instead of putting all that effort into putting `await` everywhere, why not in…

I suspect this is for two reasons: * Many popular languages today predate modern asynchronous computing. This makes async-as-default impossible because it's an afterthought * Async computing is harder to learn and confusing for those just picking up the language. There are some pretty major ergonomics issues that you have to solve if you want anything more than a DSL to achieve noteworthy adoption levels.

> Many popular languages today predate modern asynchronous computing. This makes async-as-default impossible because it's an afterthought

Here's a rough sketch of how this can be achieved for a language that already has event loops -

Transpile the following -

    void main() {
        Future xFuture = async doSomething()
        int x = doSomething()
         
        assert(await xFuture == x);
    }
Into -

    Future main() async {
        Future xFuture = (() async => await doSomething())();
        int x = await doSomething();
      
        assert(await xFuture == x);
    }
Now people can keep using a pre-existing `doSomething()` (that's either async or sync) and its compatible with our new caller based async statement.

BTW that transpiler output is runnable dart code actually works.

I know this has tons of edge cases that this simple example doesn't capture, but it's definitely "possible".

Re: What Color Is Your Function? (2015)

#74

Still we need a language where every function call is async and the runtime decides what to inline.

Looking at my async code, I just don't see how that would make things more clear instead of less clear. For example, something as simple as: const result1 = promise() // start this promise first but don't await it yet. // ... const results = await Promise.map([promise(), promise(), result1])

It's more that you don't need to add 'async' to each function, because every function is async. And working with promises automatically maps over them. Then if you want to 'unbox' the Promise you'd have a keyword, like await.

So instead of a.map(v => f(v)) ... or Promise.map(a, f) ... or a.map(f) ... it's just f(a).

If you then want the value, in the end, if at all (e.g. the web framework understands Promise and you return a Promise in your controller and not a value, you don't need await at all).

You'd need some syntax for Promise.all(...) though to "join" concurrent executions again.

The language should probably have some syntax for multiple writers, e.g. and atomic compareAndSet.

   shared v = { .... }
   v.change(v, f) 
where f has no side effects and can be executed again. With IO (DB) one would need support for idempotency ala Stripe.

But your functions would not have colors.

Re: What Color Is Your Function? (2015)

#75
post #72
post #67

Earlier quoted context omitted.

I have thinking something similar but with generators: //A async candidate fun read_lines(file): for line in file: yield line } let lines = read_lines("hello.txt").await //turn async I wonder why something like this is not used. Exist a bad interaction that could arise from this? Maybe about how nest stuff? fun uppercase_lines(file): for line in read_lines(file): yield line let lines = uppercase_lines("hello.txt").aw…

How do you plan on making this concurrent? Generators can defer computation for later, but that doesn't magically make them async.

The assumption is that exist a desugaring step in the compiler/interpreted to async/await/futures.

Re: What Color Is Your Function? (2015)

#76
post #49
post #43

I think this is just a matter of perspective and actually having different colors is a good thing: Thinking in terms of functional programming / category theory, this coloring seems to boil down to working with different arrows. The coloring in this case would correspont to signifying the target category of the arrows `arr/lift` function (For example async functions in JS should be morphisms in smth like the Kleisli…

I have some vague knowledge of category theory, but not enough to follow your argument here. Especially the concept of an 'Arrow' could you explain more clearly what an arrow, and an arrow comprehension is?

https://en.wikipedia.org/wiki/Arrow_(computer_science)

https://en.wikipedia.org/wiki/List_comprehension

https://www.haskell.org/arrows/syntax.html

https://www.sciencedirect.com/science/article/pii/S157106611...

Re: What Color Is Your Function? (2015)

#77

Earlier quoted context omitted.

> 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…

> 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++ ha…

> 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)

#78

Earlier quoted context omitted.

> 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…

> 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++ ha…

>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.

Re: What Color Is Your Function? (2015)

#79

Earlier quoted context omitted.

> A function has a single exit entry point and a single exit point and no state. If you call a function, you run its body. > A coroutine has several entry points and exit points, and an internal state. If you call a coroutine, you make an instanciation (the body doesn't run). Sort of, in some languages, but it doesn't have to work that way. First, let's note that "blues" do have state, and they put it on the stack. S…

Yes but this require the entire runtime to be designed that way from the start. It cannot be applied to legacy languages. Just like you could not add a borrow checker to C without creating 2 worlds in the C community. But you can design Rust with this in mind. A design always has a context. Also, make a giant coroutine has a performance price, because any line is a potiential context switching.

You can redo a runtime while keeping the actual language the same.

The reason you wouldn't do it in C is that there are so many implementations and only a couple of them will actually update. In most languages the extra difficulty for retrofitting it would probably be less than the difficulty of designing and implementing it in the first place.

> Also, make a giant coroutine has a performance price, because any line is a potiential context switching.

A notable one? You need a stack anyway, and you don't really have to change anything to make it switchable.

You have to avoid taking a mutex and then calling into arbitrary code, but that was already a terrible idea.

Re: What Color Is Your Function? (2015)

#80
post #26

Earlier quoted context omitted.

Isn't there still a key difference there? Inserting the latter snippet in your Golang function does not require you to change anything about the function's definition or calling conventions. From the outside, it continues looking and behaving exactly as any other (synchronous) Golang function. On the other hand, if your JS function contains an await, then it must be made async and every invocation of it must be made…

> Inserting the latter snippet in your Golang function does not require you to change anything about the function's definition or calling conventions. Neither does the C# snippet. In fact, if you want to extract the result of the function call as well, you can still keep you API as is in C#, and use Task.wait() or something similar to block execution until the task is finished, and read its result. In Go there is no…

> Neither does the C# snippet. In fact, if you want to extract the result of the function call as well, you can still keep you API as is in C#, and use Task.wait() or something similar to block execution until the task is finished, and read its result.

You can't if you want to free the OS thread while waiting for result.

Post reply on HN