Hacker Newsnew | past | comments | ask | show | jobs | submit | rendaw's commentslogin

I really don't like this article. It has a catchy, profound-sounding title that people bandy about to argue against stuff they don't like.

All functions, even non-async functions, are colored. In any large system codebase you'll have functions that can only be called in certain situations, with the right setup, whatever, and if you're lucky this is communicated by types but regardless those restrictions can't be avoided. It's easy to call low-restriction functions from high-restriction ones and not the other way around.

Furthermore, it's not like the alternative to explicit await doesn't have issues too (that the article doesn't mention). There is inherent complexity, it's a tradeoff, you can't just syntax it away.


What I like least about this article is that it's completely soured the entire context of asynchronous programming. Invariably, any time someone discusses design of an async functionality, function coloring is brought up, with almost no analysis as to how it applies and why it's a good or bad thing. (Ironically, I probably see more in-depth analysis these days as to why this article isn't apropos than why it is when this happens.) It's just reduced to "anything that makes a separation between async and sync is function coloring and that's automatically bad." The existence of any sort of trade-off, or really, the entire meat of the article, is just completely ignored.

One thing that can be better called out is that this issue of function coloring isn't just an async problem. Exceptions cause function coloring--and not just Java's controversial checked exceptions. An infallible/fallible domain split is function coloring. Javascript's async handling is called out not because it's doing the function coloring but because--in 2015--the tools that existed for dealing with async code in JS libraries were really, really bad, largely reliant on callback hell. Promises and the async/await keyword fix most of the issues, and the ones that aren't fixed boil down to the fundamental issue that an asynchronous event-loop model and a synchronous batch model are just different programming paradigms to begin with.


The problem with function color exists when you can't abstract over it[1]

Some statically typed languages (I believe both Haskell, ocaml) have powerful type system that allow abstracting over function types and function colors. Color is not an issue here.

Some other statically typed languages (C#, rust, and C++ (at least with the built-in stackless coroutines)) can abstract over types but not over colors. This is a problem.

Some statically typed languages (Go) do not encode async-ness statically, so it is not an issue[2].

Some dynamically typed languages (scheme, Lua, lisp) also do not encode async-ness statically. Everything is fine.

Finally there are some dynamically typed languages (python, js) that, eskew static types but for some reason still decide to encode async-ness statically. For me this is the most bizarre decision, especially as some of the justifications for static async-ness (performance, memory usage) are less relevant.

[1] for example, a litmus test is being able to implement an higher order function that inherits its color from one of its parameters. Essentially this is the problem of generically turning an internal iterator to an external one.

[2] fundamentally in these languages continuations are first class values that can be passed around, so the asyncness is naturally not bound to the function that created a continuation.

Edit: you can in principle abstract away color in any async language by simply assuming that any function call is async and await it. Then sync functions can trivially be made async. But at this point async annotations no longer convey any useful property: the language might as well implicitly await any function and require call-site annotations for diverging control flow or reentrancy requirements. More practically as languages with async evolve and grow asyncness becomes pervasive and pushes away any sync component.


> The problem with function color exists when you can't abstract over it

Hopefully it's safe read this as there's no common static type between function and async function meaning APIs (that take functions as arguments) have to provide seperate methods (or overloading) for these different colours.

Like in typescript you can write `<T>(f: () => T) => T` because an async function statically is just the return type wrapped in a Promise, not something like `async () => T` you can still pass in an async function as an argument.

I think that's a reasonable thing to take issue with, and its _possibly_ an avoidable design problem. That said I can see it being less avoidable if the async function requires some special kind of invocation (like being associated with some kind of async runtime and its a compiled language).

When I see people bring the issue of function colouring, the focus tends to be on the fact that a function is no longer interchangeable with a sync function and now you have to handle a promise, which I personally find unconvincing if the return type really should be a promise then it shouldn't be interchangeable with a sync function.


Your first paragraph links having the colour in the type system as allowing you to write functions that take arguments of parametric colour; your last paragraph says you're unconvinced that you might also like to write functions that return results of parametric colour.

An example: a vector of things to a thing of a vector, for "thing" in (promise, option, result<E>, ...). Such a function should only really return a promise if it's given a vector of promises, and, with an interface that "thing" supports, can be written generically for all those things.

(In Rust, there are separate implementations of that for Option and for Future.)

Higher-kinded types are the (a?) design solution, but they _do_ come at a cost, and for some that cost is higher than the cost of colours.


I am so happy I have never heard anyone IRL say colored functions. It would annoy me. The concept is interesting but like all engineering it is a trade off. In Node amd Go you don't get a choice anyway. In C# you might choose based on performance thinking of thread pools etc IIRC.

When programming in Node I find in practice async and "colored functions" no issue especially with async await. Except for performance issues they come with sometimes but not at a programming level.


> When programming in Node I find in practice async and "colored functions" no issue especially with async await. Except for performance issues they come with sometimes but not at a programming level.

JS solves this problem in two ways in the ecosystem:

- basically saying "all functions must be async" in practice

- allowing you to await a non-awaitable ("await 3" is valid)

so library authors can "force" async/await, but users don't actually have to interact with it when they don't need to. But "everything" being async/await means it's all 'basically fine' anyways

There's also the fact that JS libraries tend to be "pass in a bunch of callbacks" vs, say, Python's "override this class". It makes it much easier for libraries to have everything be async and have it really not get in the way.

Python libs tend to have much larger API surfaces due to how OOP works. So async-y internals works are harder to isolate cleanly without breaking the public API. But if you make your API "async-first" then the debugging experience in Python is miserable (try pdb'ing your way through awaitables....)

Even here though there are problems. For example, I've tried in the past to replace some lib with a more performant WASM-y thing. But it couldn't be a drop in replacement because the original library was a sync-only API, and the replacement was async!

Something very silly: you write "function add(x, y) { return x+y }". A bunch of people do things like "add(add(x, y), z)" everywhere. You find out you could make "add" "better" with async/await. You now have to get all callers to rewrite.

So what everyone does is just throw _everything_ in to the async/await pile. Which... I guess is fine but I personally dislike writing "await add(await add(x,y)), z)".

(aside: Rust's postfix await at least makes this kinda refactor less annoying)


The Node world was built with asynchronicity in mind. First via callbacks, then Promises, then async/await (Promise-based), so it feels natural now.

But if you take Python (for example), it's a shitshow. You usually have two versions of the same API, split by function name, client, package, or namespace: `foo` and `afoo`, where the a-prefixed one is async and meant to be used inside async function call chains, and the other one is the blocking version for non-async chains (which are still very much in use). It's a pain to develop for, to maintain, to scale, everything.


what I like least about this article is how people seem to just substitute some other, usually theory, notion of what function coloring is to elide the argument (usually to excuse their favorite PL) without actually RTFA. The article is about ergonomics, not PL theory.

> Exceptions cause function coloring

do they? Do they?

1) Every function has a color

2) The way you call a function depends on its color

3) You can only call a red function from within another red function

4) Red functions are more painful to call

5) Some core library functions are red


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 result some style guides forbid them entirely. Like async, the biggest problem with exceptions were the ergonomics.


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

> Ex: If I'm writing a tool to try to analyze and recommend music that has to handle multiple different file types, I might catch an MP3 library's Mp3TagCorruptException and wrap it into my own FileFormatException.

This would reduce the temptation for developers to ruin the type-safety characteristics by wrapping everything in a RuntimeException just to get the ticket out the door.

[0] https://news.ycombinator.com/item?id=42946597


The problem with checked exceptions is that they don't compose with the rest of the type system. Hence the infamous problems with things like Streams. Result types have basically all the virtues of checked exceptions without the problems.

Result types do have one problem that checked exceptions don’t. Checked exceptions automatically combine into union types in a throws or catch clause. I haven’t seen a language that lets you be generic like that.

    T fn() throws E, F, G
vs

    Result<T, E | F | G> // not even Rust lets you do this.

That's more a consequence of Rust needing its tagged unions declared up front so it can lay them out consistently in memory without runtime type information. Python and TypeScript have untagged unions (that are discriminated at runtime by the RTTI attached to all objects in the underlying dynamic language); they don't happen to have an equivalent of Rust's ? operator, but if they did it'd work like you're describing.

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.


> too many kinds of exceptions to choose from

I don't understand, why would you need to pick a checked exception? It's the dual or mirror of feeling paralyzed over a return-type because there are "too many kinds of Object to choose from."

If you're writing a CrystalBall class with a gaze_deeply() method, you'll probably return your own VisionResult (extends Object) unless it throws your TooCloudedException (extends Exception).

When someone else writes a wrapper or higher-level layer that uses your code, then it'll be up to them to convert or wrap those results and exceptions into something suitable for their level of abstraction.

> Returning a new kind of error is always a local change

One of my axioms here is that return-values and checked-exceptions are two sides of the same architectural type-system coin. While I'm not familiar with Go, that sounds like something that would be a symptom of bad architecture if it occurred for return values.

In other words, suppose all Java methods always returned Object [0]. That would also ensure that a new return type is "always a local change" to the compiler, but I think most developers would be rightly horrified if they came across code that worked that way.

[0] Let's ignore Java primitives for now.


> you'll probably return your own VisionResult (extends Object) unless it throws your TooCloudedException (extends Exception).

> When someone else writes a wrapper or higher-level layer that uses your code, then it'll be up to them to convert or wrap those results and exceptions into something suitable for their level of abstraction.

Why though? What do you gain other than longer stacktraces with all those wrappers? People always trot out some theoretical notion that a caller is going to catch that framework's different exceptions and handle them differently, but have you ever seen calling code that actually did that?

> In other words, suppose all Java methods always returned Object [0]. That would also ensure that a new return type is "always a local change" to the compiler, but I think most developers would be rightly horrified if they came across code that worked that way.

There are many different kinds of values. There really aren't that many different kinds of error - there's "transient error that you might want to retry", "programmer called the API wrong", and that's about it, most other cases (like bad user input) probably shouldn't be exceptions.


This isn't like returning Object. It's more like returning a String. After using a language with a common String type, who wants to go back to writing code to convert between between different kinds of strings? Having to choose among different string implementations because there's no standard usually leads to boilerplate code doing conversions at the borders.

Usually you just want to propagate or log errors, so having a generic error interface is sufficient. It's true that in Java, you can wrap exceptions, but that's extra boilerplate.

(And yes, Go does notoriously have error propagation boilerplate that they should fix, but that isn't a type system problem.)


#3 is not satisfied, as you noted in #2. You can call `throws` methods from non-`throws` methods by wrapping the call in a try catch, and `throws` methods can call non-`throws`. There isn't an exclusivity asymmetry like there is for JavaScript async.

That only applies to Javascript, which, is mostly only red functions anyways (there are no blocking apis in javascript). Javascript doesn't have the coloring problem in the way Python or Rust has it.

In Python, you can wrap the call with asyncio.to_thread, in rust with tokio::spawn_blocking.


I think you got it backwards: JavaScript has the coloring problem while other languages don't.

"Red functions are more painful to call" alludes to async functions. Every await yields back to the event loop, which adds overhead. Making every function red/async adds a performance cost (and makes it harder to reason about race conditions), which is why JavaScript has a mix of blue and red functions.

Other languages can escape the "red functions can only be called by red functions" trap, like Python asyncio.run or Tokio block_on. JavaScript has no such alternative, not even in Node. Therefore, Python and Rust don't have function coloring, but JavaScript does.


I think the big difference is that in an application that cares about throughput and being non-blocking simply blocking isn’t really possible as it affects the whole performance of the application. Wrapping a checked exception in an unchecked one doesn’t do that.

Ok, sorry it's been about 20 years since I last javad IIRC you didn't have to declare exceptions in your function signatures. However, wrapping in try/catch seems to violate #3. Try catch is not a heavy lift of a seam between red and blue

To be fair, #3 seems to have shades of grey. In some pls, you can call an async function from a sync one by wrapping it in a whole damn event loop system. Should that count?


> Ok, sorry it's been about 20 years since I last javad and you didn't have to declare exceptions in your function signatures.

You're probably remembering RuntimeExceptions, which are a subgroup [0] that are exempt from "checking" by the compiler, which means it does not require method signatures to declare "I might emit this."

[0] https://docs.oracle.com/en/java/javase/26/docs/api/java.base...


You can declare your runtime exceptions too. The compiler won’t enforce you to catch them though.

    void fn() throws IllegalStateException

Your question just kicks the can down the road. The problem with the article, to me, is the author doesn't want to accept a certain amount of complexity and has erected arbitrary road blocks.

To you, a "whole damn event loop system" is too high a price to pay, but try/catch is not. The complexity of exceptions is invisible to you. However there are certain environments (e.g. FFI) where I dont want "the whole damn exception runtime".


Maybe author do not want to hide the complexity in functions?

i mean no? the coloring problem appears to be solvable in zig. i maintain an FFI binding library for the BEAM vm, where once zig finishes its stackless coroutine support, the same zig function written once should be fully interchangeable between non-async, threaded-async, or async-with-yieldpoints-wrapped-in-the-BEAM's-scheduler.

> Try catch is not a heavy lift of a seam between red and blue

> To be fair, #3 seems to have shades of grey. In some pls, you can call an async function from a sync one by wrapping it in a whole damn event loop system. Should that count?

I think you have to count any extra overhead where you can't just write f(), including try/catch. It's always possible to call whatever kind of function from whatever other kind of function if you put enough effort and hackery in, so if we can't use functions of kind x in functions of kind y as normal "f()" function calls then that has to be what we mean by colouring.


Let's go through the list:

> 1) Every function has a color

Every function either throws an exception to indicate failure or doesn't. There's actually several different function colors available here, based on how failure is indicated: throwing exception, aborting the process, composite return value, error code return value, global errno-like variable, error code as a parameter, ....

> 2) The way you call a function depends on its color

See above.

> 3) You can only call a red function from within another red function

Some of the failure methods, like aborting on failure, cannot be converted to another mode at all (or only with very great difficulty). Others, like exceptions and errno-based routines, come with environmental constraints that could be contained by an error conversion routine in theory but may be precluded due to how the system as a whole works (e.g., a global variable errno doesn't play well with threads). Which isn't quite the same thing, but then again, "red function" here is async function, and the call-async-from-sync variant is the easier one to pull off (you spin the event loop), and has roughly the same issues as trying to box an exception routine: it only works if the system as a whole has mechanisms to make it work.

> 4) Red functions are more painful to call

Okay, you've got me here... the exception routines are the easier ones to call, syntactically than non-exception-based ones. Internally in the optimizer, however, exceptions are definitely the worst form (even errno somehow ends up working out better, and that's also deeply problematic).

> 5) Some core library functions are red

Oh yes, standard libraries love using a mix of all of these error-handling routines. Look up C++ <filesystem> for example.


Failing is a color, but throwing an exception isn't. An exception-throwing computation can easily embedded in a computation that doesn't throw - you can catch and return null, etc. But very rarely can a computation that may fail be part of a computation that may not fail.

this article seems like a nice view into what things were like in 2015 but we've come along way since then.

Actually, it's not async programming. It's only async programming the way JS does it... which is unwinding the entire stack, and then starting another stack on the next tick.

Instead, many languages have fibers / coroutines / etc. which simply start new stacks elsewhere, and capture the context.


What I like most about the article is that it drove the conversation to realising that async is just a poor reimplementation of threads, and put the focus back on how to do threads faster.

Probably you are right except for some pathological scenarios. Threads and green threads and models where you have have 10000s of threads and not even hit the cardio fat burning zone.

Yep. It's not an async vs not async thing. The way some people talk about it, you'd think the async keyword was at fault. It's all about whether a function is callable in some context.

Passing in the context as an argument or making it a global variable or returning a monad doesn't do anything to uncolor the function. What's the difference between `async function f()` and `function f(eventloop, callback)`? Only syntax.

Not to mention there's lots of colors unrelated to async, that most languages don't type at all. And if you use the wrong one, your program just doesn't work correctly at runtime. Thread-safe vs thread-unsafe. Blocking vs non-blocking. May throw/panic vs won't throw/panic. May fail/return null vs infallible.


> Passing in the context as an argument or making it a global variable or returning a monad doesn't do anything to uncolor the function. What's the difference between `async function f()` and `function f(eventloop, callback)`? Only syntax.

"Only syntax" is assuming, mistakenly, that syntax doesn't matter.

Also there is a big semantic difference there.. that being in one case you have the flexibility of the passed in parameters taking different forms vs. the static 'async' statement.

It is not strictly an async thing, but a general rule that additional keywords are less powerful than parameters in all cases. Ask any Lisp developer what the difference is..


Ante has some points on this issue: https://antelang.org/blog/why_effects. All of this is just different syntax in other languages and solved but the abstraction provided seems to be neater.

I believe a clearer example would be: `async function f(): Foo` vs `function f(): Future<Foo>`. Isn't it how it works inside anyway?

> What's the difference between `async function f()` and `function f(eventloop, callback)`? Only syntax.

Negative.

what is the "async prefix" equivalent of the following?

global e: eventloop;

noasync fn parent()

  childfn(e)
end

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 in, for example, Rust or C#.

The article has the same properties that many successful people IRL have: it makes a certain ick very easy to feel and understand, but it doesn't offer much in terms of profound knowledge.

What it does offer though is a perfect spark of discussion, making people who, for example, only know the single-threaded async-await from JS, consider the sheer possibility of other approaches. I am among those people with a limited horizon, presupposing that "knowing" means deep experience to you.

I have some superficial experience with Java physical threads, also with C#, but $job uses JS/TS.

And even in JS, none of this is trivial in my mind.

Consider the deceptively simple question of a kind of "mutex" that enables an async function or method to control concurrency of its own invocation.

The answer to this simple question (queueing promises and clean rejection handling) is already far from trivial, involves the microtask queue, and shows where the mental model of JS-async-await begins to deteriorate.


While non-trivial in several ways, there are standard Web APIs such as Web Workers, Web Audio API, OffscreenCanvas, SharedArrayBuffer, etc. which help to construct modern, multi-threaded applications in JavaScript. Hopefully today, any experienced JavaScript-focused web developer should be experienced with more async paradigms than just async/await.

I think these APIs address real issues, but it also makes the entire stack more complex when integrated language support might be better for some features. But, keeping things separate does mean JavaScript as a language is fairly portable and backwards-compatible, if you ignore Web API support. Still, a lot of other languages feel more batteries-included and have more elegant multi-threaded async fundamentals.


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


I'm fairly sure the author was making reference to the famous article What Color Are Your Bits? https://ansuz.sooke.bc.ca/entry/23 which is even more abstract, because some large numbers are coloured "legal" and the same large numbers are coloured "illegal", based on where you got the numbers from.

All functions are not coloured, don't try to wriggle out of it by generalising. This article is a specific complaint about Javascript. Javascript is a hack on top of a hack. Its async/await is crap. Javascript requires this "colouring" in a way that C#, Java, Go, Python, Ruby, C, C++, Rust, etc. don't, because they don't have to pretend they're a single-threaded event processing loop, while Javascript does.


I haven't used python recently, but in the days of asyncio it was very much "painful" (to borrow the article's verbage) to use, precisely because of the five criteria in the article.

Painfulness isn't the main issue with colored functions, it only explains why we don't make every function red (async).

The main issue is that sync functions can't call async functions, but in Python, you can bypass that restriction with asyncio.run.


Fta

4) Red functions are more painful to call

I guess for me if I reach back to my memory my real problem with asyncio was that it used decorators and wrapping my head around how it was a crazy abuse of generators, completely broke my internal model of how python works (and also how at the time debugging became problematic), and maybe not so much the ergonomics, so strictly speaking a different set of ergonomic problems than in the colored function article


Agreed. Every time this article makes the rounds, people get hung up on the syntax, and not the performance considerations discussed towards the end. Nystrom wrote a whole book on interpreters, and his main criticism here is the big mess of chained closures this makes at runtime, compared with pausing/resuming green threads.

Something I have been thinking about recently is this: metaphors are often a way that authors use to make an argument in a way that is more engaging than: here is fact A, here is fact B, etc. But some metaphors are so strong that they make a stronger argument than the actual facts! And when you hear an argument with such a strong metaphor, you can often end up feeling very convinced of a particular point, even though the point itself isn't worth feeling so convicted over! I feel a very strong metaphor hijacks the emotional part of your brain before the rational part of your brain can figure out if it actually makes sense or not.

In the case of this article, the metaphor is extremely strong. Colored functions! What could be dumber! You feel like you see the world clearly after reading the article, and you pity the people who can't see the clear categorization of functions like you can! But, in reality, when has this ever annoyed me? Never! I've worked with async functions in JS/TS for over a decade and there hasn't been a single time in my entire life where I've been frustrated or backed into a corner because I wanted to call an async function from a sync one. At worst I've had to tack on "async" to a couple function declarations and be on with my day.


I do find it annoying. Let's say in JS I have `result = list.map(f)` but now `f` returns a Promise.

`result = await Promise.all(list.map(f))` is less pleasant to read. And before writing it, I have to think if I want the `f` function to execute concurrently across all entries of the list, or one at a time: `for (const elem of list) { await f2(elem) }`.

Or maybe I should use a library like `p-map` and carefully set the concurrency level. Or maybe I should create a bulk version of `f` that takes an array and is more efficient than calling `f` N times.

And don't get me started when there's `list.forEach(f)` and `f` becomes async, so now it executes concurrently for all elements, and the engineer who made the change didn't realize it.

And then there's Async Generators ...


> I have to think if I want the `f` function to execute concurrently across all entries of the list, or one at a time: `for (const elem of list) { await f2(elem) }`.

I'd consider that a positive rather than a negative. That's an important question to think about (usually) and I want the type system to help remind me.


> It's easy to call low-restriction functions from high-restriction ones and not the other way around.

do you have an example in mind when you say this? I think there's some unique messes with async/await (especially when combined with OOP and extension points... either your extension points _all_ have to be async or you have to have awkward restrictions), in a way that, say, permission checks don't have IMO.

a syntax-less "await function" that climbs up the stack trace to whoever is waiting and holds onto the suspension _is_ a way out of the problem the article describes. Requires runtime support but it effectively means you can always suspend.


Every time this is posted, it’s worth reminding: async functions in JavaScript are the correct design, and the people who did it deserve praise.

C# (and F# before it) got it (mostly) right. JS did a shallow copy of it, messing up some details, making it harder to use for certain things.

Making await flatten promises was kind of questionable. But that’s my main beef.

Async functions are an ad hoc, informally-specified, bug-ridden, slow implementation of half of monad support.

> All functions, even non-async functions, are colored.

Not by the analogy laid out in the article.


I agree it is a dumb article.

It made a big fuzz back then but to me it's excellent as a litmus test. Learning async grammar in Javascript takes you an afternoon, after a week it should become familiar. If someone is unable to grasp that ... well, that tells you where they stand.


I'd agree, but in this case they were able to write a blog post that got (as of now) 300+ points and 200+ comments with the content of their work.

Are the people who pay lots of money not fans?

Get a ticket to a premium area and count the "influencers" who are there just to produce content, instead of enjoying the show

I've been playing around with reviving radiosity for incremental GI for low-poly scenes, and this sounds very similar (and probably much better). I put a camera on each lightmap texel, rendered the scene, then summed the pixels (roughly) in the render to get the light. I chose the "slow" approach, where lighting took several seconds, but was done idly. And then once the lightmap had a certain amount of stability I'd stop the light calculations until the scene changed.

It sounds like the advantages here are:

- Optimized sampling, rather than just every lightmap texel. My idea was to tie the lightmap to LOD, but I feel like this is much smarter.

- Optimized light accumulation, dedicating more resolution to high-light areas to reduce noise

It seems like it has a more advanced "stability" calculation.

Things that are the same:

- Lighting is still incremental - when they e.g. change the light direction, even with optimizations, there's still some ghost light that slowly moves over so I'm not sure how this would work in really dynamic situations (car traffic)

Things that are different:

- It looks like the light data is cached based on the current view. I store light for the whole scene, so there's no light fluctuation when doing camera movement/rotation. I think the tradeoff here is the view-relative caching is probably more optimized (light detail is view invariant) - I think that's mostly important for HD-style assets.

Limitations of both, IIUC:

- Reflections, water, etc. Radiosity is diffuse lighting only. I think you can combine it with other hacks like screen space reflections though


The title says it's a new filesystem, you either need to use fuse or a kernel module.


I mean not really. There is a FUSE implementation, but you need an enterprise account https://docs.lakefs.io/v1.60/reference/mount/

I’m not seeing a kernel module anywhere..


How useful is it though if it doesn't resemble what it will sound like when actually played? IIUC the difference between this and a human playing is to some degree subtle. But the audio difference between different violin designs will also be subtle.


I think you're saying that there are businesses that rely on cheap air transportation that are very valuable, but at the same time couldn't afford higher air fees.

But that's a contradiction. If they are valuable, their customers would pay more for their services - that's the definition of valuable. And if their customers would pay more, they could afford higher air fees.


No, all I’m saying is that air travel is so different than any other kind of travel, that it is very special, and borderline magical. Saying something like “nothing magical about air travel, things and people would still travel around the globe” is very reductive. I’m not giving my opinion on subsidies.


How are you not giving an opinion on subsidies?

Person 1. "Airline service is more valuable than people will pay for, it's a genuine force multiplier that is unaffordable without being subsidized"

Person 2. "Airlines are not magical, people and goods will move another way, so it doesn't need subsidy".

You: "Airlines are magical. Those things cannot happen another way."

There's three conclusions for what you think: 1) that airlines are special and magical and doing something which cannot be done another way, but that has no value and airlines can go away. That's incoherent. 2) Airlines are both affordable and profitable. That doesn't seem to be true and needs some supporting. 3) Airlines are doing something uniquely 'magically' valuable, they are not profitable, then they need subsidising.


Your point 3 is a non sequitur. If air travel is magical and valuable, that doesn’t automatically mean it needs subsidizing. We sometimes allow magical and valuable things to go away if we find them not to be popular enough to garner widespread political support.

My statement is correcting a fact (descriptive) not proposing what to do about it (I.e. not prescriptive).

It’s very hard to imagine what the world would look like without subsidized air travel. I have to think long and hard to figure out if subsidies would actually be sensible for something like this. I can be convinced either way right now, but it would take a lot of good historical data on something very similar, perhaps has to be specifically air travel in countries that do and don’t subsidize it, and their economic outcomes, controlled for other factors.

But saying that air travel is somehow the same in kind as other kinds of travel is incredibly shallow and reductive. We get to travel orders of magnitude faster and to places we wouldn’t even be able to reach otherwise.


I don't think GP was criticizing you for liking decaf. Just pointing out that the decaf process may have affects on the beneficial compounds that aren't caffeine.


True. I love black coffee, decaf or not. Just thought it was worth pointing out since the thread is about health benefits


I was curious seeing this thread, and I just looked and don't get it either. AFAICT the CP/M references could have been entirely omitted and nothing in the narrative about TMP and TEMP would change.


Except that DOS was made to have its first programs ported from CP/M, so it’s relevant to explain that there were no environment variables to inherit from CP/M and no developer habits or program standards to inherit from CP/M programs.


Which is irrelevant to TMP or TEMP.

It could simply be: When envars were added to MSDOS…


Multics had envars in the 1960s and Unix in the 1970s, why were they ‘added’ to DOS when it was so close to an older OS, why didn’t it inherit them from CP/M? Did it get TMP from CP/M and introduce TEMP because computers were bigger but then?


Did Multics actually have something really similar to the much later envars introduced in Unix?


Those questions appear awfully overfit to the current blog post.


That comment feels awfully cherry-picked to the perfect untestable rebuttal based on what you want to be true.

How did you measure the fitness and decide it was 'over'?


Why did you need to show impact? Was it for internal budgeting?


(no impact == no staffing, no resource allocation) -> Leadership: "please, stop working on it"


How else do you get money for anything in your organization?


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: