(or: just solicit feedback in a space frequented by designers, and harness the power of being wrong on the internet ;)
75 karma · joined May 2, 2025
(or: just solicit feedback in a space frequented by designers, and harness the power of being wrong on the internet ;)
Of course, as you said, stackful coroutines come with runtime overhead. But that's the tradeoff, and I'm sure they are substantially more efficient (modulo FFI calls) than the equivalent async-everywhere code would be in typical JS or Python runtimes.
My "you have to do it manually" comment comes from some other peeves I have with Go. I guess the language designers were just hyper-focused on syscall concurrency and memory management (traditionally hard problems in server code), because Go does fare well on those specific fronts.
I'm with you that function "coloring" (monads in the type system) can be unergonomic and painful.
> ... isn't that what Go is? I think out of all languages I use extensively, Go is the only one that doesn't suffer from the […] coloring nightmare.
Because it doesn't have Future/Promise/async as a built-in abstraction?
If my function returns data via a channel, that's still incompatible with an alternate version of the function that returns data normally. The channel version doesn't block the caller, but the caller has to wait for results explicitly; meanwhile, the regular version would block the caller, but once it's done, consuming the result is trivial.
Much of the simplicity of Go comes at the expense of doing everything (awaiting results, handling errors, …) manually, every damn time, because there's no language facility for it and the type system isn't powerful enough to make your own monadic abstractions. I know proponents of Go tend to argue this is a good thing, and it has merits. But making colorful functions wear a black-and-white trenchcoat in public doesn't solve the underlying problem.
(goroutines are nice, though.)
This is important, thank you for mentioning it: actions have consequences besides those that motivated the action. I don't like when people say "<actor> did <action>, and it leads to this nefarious outcome, therefore look how evil <actor> must be". Yes, there is always a chance that <actor> really is a scheming, cartoonish villain who intended that outcome all along. But how likely is it that <actor> is just naive, or careless, or overly optimistic?
Of course, the truth is almost certainly somewhere in the middle: familiarity with a hard-to-learn UI as a point of friction that promotes lock-in may not be a goal, but when it manifests, it doesn't hurt the business, so no one does anything about it. Does that mean the designers should be called out for it? If the effect is damaging enough to the collective interest, then maybe yes. But we needn't assume nefarious intentions to do so.
Then again, everyone thinks their own actions are justified within their own value system, and corporate values do tend toward the common denominator (usually involving profit-making). Maybe the world just has way more cartoonish villains than I give it credit for.
In the lingo, expressions are evaluated by repeatedly getting "rewritten", according to rules called reduction rules. e.g., (1 + 2) + 4 might get rewritten to 3 + 4, which would then get rewritten to 7.
There are two sorts of these rules. There are "congruence" rules, which direct where work is to be done ("which subexpression to evaluate next?"); and then there are "computation" rules (as Pierce [1] calls them), which actually rewrite the expression, and thus change the program state.
"Strict"/"non-lazy" languages (virtually every popular general-purpose language? except Haskell) are full of congruence rules – all subexpressions must be fully evaluated before a parent expression can be evaluated. The important exceptions are special constructs like conditionals and indefinite loops.
For conditionals in particular, a computation rule will kick in before congruence rules direct all subexpressions to be evaluated. This prunes the expression tree, now in a very literal sense.
[1]: Benjamin C. Pierce, Types and Programming Languages (recommended!)