Live data from Hacker News

What color is your function? (2015)

journal.stuffwithstuff.com

81–90 of 198 posts

Re: What color is your function? (2015)

#81

Earlier quoted context omitted.

There are no function colors in Go in the way being discussed. Every function can be spawned as a go routine, every function can spawn go routines.

The functions are still coloured, just implicitly. IYKYK to spawn a goroutine or not ts.

> The functions are still coloured, just implicitly. IYKYK to spawn a goroutine or not ts.

In Go, you can choose to either block on a function call or to execute it as a go routine. The function has no "color" in the sense of the article.

If you want to print asynchronously, you can with a `go fmt.Println("Hello")`, or you can block on that print and remove the `go `. There is no color to any function. And the function containing that, it also has no color. It can be called synchronously or spawned as a go routine, Go makes no distinction between the kinds of functions that can be used each way.

Re: What color is your function? (2015)

#82

Earlier quoted context omitted.

Performance aside (which I would argue is premature optimization, as most programs will not feel the theoretical overhead of threads), async is a bad approach for developer ergonomics. Threads are so much easier to work with and reason about than async. There are reasons to use async (like if you're in the rare case when thread overhead is noticeable), but developer ergonomics are absolutely not a reason.

Say I need the results from two expensive REST API calls, so I want to run them concurrently. Managing a thread pool you find a _better_ experience than one, two = await asyncio.gather(callOne(), callTwo()) ?

Doesn't Python support futures?

    with ThreadPoolExecutor() as executor:
        one, two = executor.map(lambda f: f(), [callOne, callTwo])
I'm sure you could write a nicer helper function that's more similar to gather as well.

Re: What color is your function? (2015)

#83

Earlier quoted context omitted.

It's an interesting repeat submission to study how HN comments change over time though. Regarding content, I agree with you. Async/Await is an amazing paradigm in JS for simplifying callback patterns and non-blocking suspense. In other programming languages, there exist other intriguing paradigms that are more elegant and emphasize other aspects of "async"; my prime example 2 would be Erlang, but I am not experienced…

> It's an interesting repeat submission to study how HN comments change over time though. We've had at least a decade of using these async/await languages and discovered function colouring isn't a problem.

[deleted]

Re: What color is your function? (2015)

#85

I've exclusively used async/await style languages for my entire life and have not once ran into this supposed problem of function colouring. Basically all IO/async work you do requires a context, does it matter if that context is a parameter or a keyword? I don't think so. The author is inventing a problem to rant about.

The one time I did run into coloring being an issue was when working with gevent/greenlets (green threads) in Python nearly a decade ago.

Implicit management of async operations is something I hope I never have to deal with again.

Re: What color is your function? (2015)

#86
post #71

Earlier quoted context omitted.

Java just makes them hard to use. They're not fully apart of the type system and they're hard to escape when you actually want to panic. Everyone around here praises Rust's result, checked exceptions are the same idea: fn someFn() -> Result T someFn() throws E fun someFn(): T | E // Kotlin's proposed error unions Checked exceptions actually compose a little better when you have a function that can throw multiple type…

Rust makes you define an enum of E, F, and G, but also provides a conversion API so you can pass any of the three and it feels like it does, at least at the site of returning the error. It also provides an error interface so sometimes you don’t need the enum, if all the types return that interface.

Wouldn't you lose a little compile time safety a little by returning the interface, like catching Exception?

i.e. as types you don't know about get introduced the compiler won't stop bad things from happening:

    catch (Exception ex) {
        switch (ex) {
            case SomeException1 se1 -> ..
            case SomeException2 se2 -> ..
            default -> throw new IllegalStateException(ex); // panic
        }
    }

Re: What color is your function? (2015)

#87
post #66

Earlier quoted context omitted.

Java's checked exceptions fit the 5 criteria: 1. It either `throws` or it doesn't 2. If the function `throws` you have to wrap it in try/catch, or make your function `throws` 3. Your function is `red` if it `throws` the same exception. 4. see (2) 5. See the FileReader class in core. Now, C++ exceptions might not satisfy all of these, but the problems CheckedExceptions were meant to solve still exist in C++ and as a r…

> Like async, the biggest problem with exceptions were the ergonomics. I know it's not a popular take, but I prefer the idea of Checked Exceptions over unchecked ones [0], and suspect current opinions would be vastly different if Java had shipped with some sweet syntactic sugar for: "If an exception that is of kind A or B or C occurs, automatically throw another checked exception X with the original exception as a ca…

The problem with Java's checked exceptions is that it has too many kinds of exceptions to choose from and they're overly specific. Compare with Go, which has a single error interface and had it from the beginning, so it's used everywhere. Returning a new kind of error is always a local change, unless it's a function that didn't previously report errors at all.

Type systems permit either standardization or fragmentation and that's an ecosystem issue. Another example is that a language without a strong consensus on which string type to use will result in a fragmented ecosystem when each library goes its own way.

Re: What color is your function? (2015)

#88
post #6

> You still can’t call a function that returns a future from synchronous code. (Well, you can, but if you do, the person who later maintains your code will invent a time machine, travel back in time to the moment that you did this and stab you in the face with a #2 pencil.) Author makes up a lie. Then lampshades it away with a colorful non sequitur. --- The alternatives that people praise like golang, have other trad…

I don't see how that's a lie; calling an async function from synchronous code is generally a mistake. There are cases where it's appropriate but it's rare

Re: What color is your function? (2015)

#89
post #3

Go doesn't have colored functions due to its nice fat runtime hiding all the async magic away for us. That makes it a pleasure to code concurrent stuff for IMHO. It does have its own similar problems though - does a function return an error? If so you are going to need to plumb the error return through all the callers. Does a function need a context.Context? Ditto. I guess you can't win them all :-)

I'd argue that Go and all other implicit async approaches do have function colours. You're much less likely to notice the colour, but in the edge cases where it can be noticed such systems are harder to work with.

Re: What color is your function? (2015)

#90
post #15
post #3

Go doesn't have colored functions due to its nice fat runtime hiding all the async magic away for us. That makes it a pleasure to code concurrent stuff for IMHO. It does have its own similar problems though - does a function return an error? If so you are going to need to plumb the error return through all the callers. Does a function need a context.Context? Ditto. I guess you can't win them all :-)

This is a subtle point that I've seen missed repeatedly, but: The reason that "color" is important is that if you have a function ten layers down in your stack that is the wrong "color", you now have to change that top-level function. There is no other option. Propagating errors up the stack is not the same, because the top-level function is not developing an error return because of the 10-level-nested function. It i…

> Propagating errors up the stack is not the same, because the top-level function is not developing an error return because of the 10-level-nested function. It is developing one because the function it called has one, and apparently, it needs to return it to its local caller. It's a local consideration ...

> By contrast, in a function coloring situation, if the color is wrong 10 layers down, you must change the calling function. It's a non-local consideration. You don't get to decide not to change it. You can't encapsulate it. You don't get a choice. It pollutes the entire stack, forcibly.

I think this is an interesting perspective, where I would raise a counterpoint. Both result types and async/await are instances of monads (the abstraction which approximates the article's idea of a function color, since you mentioned Haskell, I assume you know this). Just as you can "eliminate" the result type by explicitly handling the success and error cases, you could, theoretically, "eliminate" the async function by blocking on it. Doing so would treat the entire async subprogram, at the top-level function boundary, as synchronous IO, while the async subprogram would still benefit from concurrency internal to the function.

Compare Example #1:

    int topLevel() {
      return match fallibleSubprogram() {
        Ok(()) => 0,
        Err(_) => 255,
      };
    }

    Result fallibleSubprogram() {
      let x = f()?;
      let y = g()?;
      return h(x, y);
    }
Compare Example #2:

    int topLevel() {
      block_on(asyncSubprogram);
      return 0;
    }

    async void asyncSubprogram() {
      let promiseX = f();
      let promiseY = g();
      let [x, y] = await Promise.all([promiseX, promiseY]);
      return await h(x, y);
    }
In the above pseudo-code, you have the same program "structure," but the first uses results and the second uses promises. In the latter example, asyncSubprogram() gets called as if it were synchronous, but you still benefit from asynchronicity because f() and g() can execute concurrently within its body.

The main difference is that compared to pattern matching on Result types, programming languages typically make it unidiomatic to block on a promise. There are various reasons why this is the case, but my point is that Result types and async/await are more similar than they may initially appear.

Post reply on HN