am i the only one that thinks the answer to those recent CPS questions are all sorta lackluster
<https://langdev.stackexchange.com/questions/2079/what-are-the-disadvantages-of-using-cps-form> no offence to davislor, it's a very good answer, but i feel like there's a lot more to explore there
maybe i'll see if i can find the time to add some more
I agree that the recent CPS question has not really gotten a good answer, but I don’t think I know enough offhand to write a good one, and I haven’t taken the time to do the necessary research
I think my immediate response to the recent question is that a good answer should explain the difference between using CPS as an intermediate representation and using CPS as your actual runtime strategy
i have a hunch CPS is going to be more interesting (as a strategy) as/if effect systems start becoming more popular (especially now that OCaml has effects)
That may be true, though I think native delimited continuations seem to be growing in popularity, and I think there are lots of advantages to them over CPS (source: I added them to GHC specifically because of those advantages; people have been doing CPS in Haskell to implement them for a very long time)
I do think the fact that CPS is used to mean several different things (compiler IR, language runtime strategy, library implementation strategy) is a frequent source of confusion
It would be nice to have a canonical Q&A that thoroughly explains the distinction and talks about both Compiling with Continuations (and related work) and various implementations
I don't think there's been a useful "non-lisp" language that's gone full runtime-CPS yet, so I definitely would like to see how that might stack up perf wise against something like OCaml, but there are obvious downsides besides perf
@AlexisKing if/when I have some free time I might be able to draft up something like this, i've been reading the related works quite a bit recently
although I don't have access to a copy of the original unfortunately, so someone else might have to step in there
@blueberry Yes, I think if I were to write such an answer I would probably essentially make the case that going full runtime CPS is largely considered a toy strategy (albeit one with certain benefits if you care about making a very simple system) and is not usually what people mean when they talk about “compiling to CPS”
@blueberry That would be great! :)
As a GHC person I think I’m biased against CPS as an IR (and there’s even a tongue-in-cheek GHC paper called Compiling Without Continuations), but I’d find it very interesting to see a perspective from someone who favors it
@AlexisKing which is rather unfortunate really, because "easy" stackless execution lets you do some really neat "tricks" in the realm of async-by-default languages and effects, but alas
I do think it’s interesting that Rust async/await and similar systems basically work that way, and that would be interesting to discuss as well
Rust is a particularly interesting implementation because it is basically compiled as CPS, but because it uses structs to represent the environment, there’s a lot more copying rather than pointer chasing, which has very different tradeoffs relative to most CPS implementations
but yeah no, if your default execution model is "frequently pausing and resuming processes", you ideally want to make that as seamless as possible - no need to do stack shuffling if there's no stack
I think lightweight stackful coroutines are still pretty seamless (Go and GHC use them, and they work quite well), but they do require a runtime and a GC (because they rely on being able to move stacks to grow them)
There are definitely different tradeoffs. It’s honestly very unclear to me which approach I’d prefer in general
One of the amusing things about Rust async/await is that it can be necessary to manually introduce Box to break up the context to avoid having to copy around an absolutely massive struct every time the coroutine yields
i'm currently working on something that would ideally mirror Erlang and Go in the "auto-async" department and have been looking into CPS-likes for a runtime scheme, so I'll report back on that, but I am keeping my hopes medium-ish
@AlexisKing async rust really does fall into the Box-hole a lot, huh
I think there’s a pretty big difference between actors and async/await? There’s no await in the actor model!
My mental model of systems with true concurrency (i.e. not JavaScript) and async/await is essentially CSP with extensions, whereas I think of the Erlang model as fundamentally different (in ways with its own tradeoffs)
I think you’re just describing that Erlang concurrency is preemptive rather than cooperatively scheduled, which is true, but also applies to, say, Go or even .NET Tasks
I just don’t think that has much to do with async/await per se (though in practice many async/await implementations do use cooperative scheduling for various reasons, and I think that’s what popularized the async/await terminology)
I also don’t think Erlang has the ability to have a “channel” as distinct from a process, right? Like, you can emulate it with a process that pumps messages, but that has different characteristics
I usually think of the Erlang model as in some sense being more first-order because everything is explicitly stratified into processes, which is what allows those processes to be automatically distributed (which is something CSP can’t really do)
Yeah, I’m definitely not an expert on anything Erlang related, but I had the opportunity to chat with some Erlang people last year and they helped me understand it a bit better
@AlexisKing I’m actually looking into this now, and I think I (and by extension @EldritchConundrum) might be wrong about this, because it seems like a receive expression is allowed to have context that has to be saved if the process suspends. I guess I’m not sure how serialization works in that case
After a little more investigation, it seems like Erlang processes are in fact stackful, so they’re basically just lightweight threads, and the main restriction is just that sends never block. As far as I can tell, if a new version of a module is loaded, existing processes continue to run the old code until they return from whatever function they’re currently suspended in.
That’s actually a little more liberal than I’d realized—I was under the impression that receive was more restricted.
@EldritchConundrum It was actually one of my first functional languages, even though I never really learned how to do most of the actor stuff that makes it actually worth using
I am going to be physically at LIVE all day, but IWACO probably interests some people here, and there are some interesting-looking slots in SLE and SAS too
eyyy my a-language-something-like-c to Chef compiler just compiled its first line of code!
the code
@entry
fun main() {
let a = 1 + 1;
}
was compiled to the Chef code
test.
Ingredients.
a
0 b
1 c
d
e
f
h
i
j
k
Method.
Put b into mixing bowl.
Fold a into mixing bowl.
Put c into 5th mixing bowl.
Put a into mixing bowl.
Remove c from mixing bowl.
Fold d into mixing bowl.
V the d.
Put b into 6th mixing bowl.
Remove c from 6th mixing bowl.
Put c into 6th mixing bowl.
Stir d into the 6th mixing bowl.
Fold e into 6th mixing bowl.
Fold e into 6th mixing bowl.
Put e into mixing bowl.
Set aside.
V until ved.
Put c into 6th mixing bowl.
Put b into 6th mixing bowl.
Stir d into the 6th mixing bowl.
the intermediate is
def $state 0;
def @returns;
@returns.push 1;
while {
push $state;
lt 1;
} {
push $state;
eq 0;
if {
def $ap0t6d9db2c35480dbcd;
push 1;
push 1;
add { nop; };
pop $ap0t6d9db2c35480dbcd;
del $ap0t6d9db2c35480dbcd;
@returns.pop $state;
};
};
Ah, you can indeed do "naive" async/await in Erlang (Here Elixer, but you could obviously do the same in Erlang) <https://hexdocs.pm/elixir/1.13/Task.html#async/1>
@Ginger What’s funny about this is that Haskell (even Haskell 98) literally has a language feature called “guards” that work exactly like this. I don’t know what @DannyuNDos is on about
kotlin's when is not as powerful as F#'s match, but it does type refinement and it's usually good enough that it could be used instead of if without looking bad
in OO everything is an object too. if you design your language around a single thing, everything can be that thing (that doesn't mean it's a good idea)
hmm... maybe in the 90s, the languages that made OO mainstream were cowards because they didn't want to alienate existing programmers, who didn't want to think of primitive types as objects? or maybe because it didn't go well with C++'s performance concerns...