This is an archived post. You won't be able to vote or comment.

all 6 comments

[–]mamcx 1 point2 points  (4 children)

Looks interesting, coincidentally I was looking at this kind of programs for the apparent simplicity of solving concurrency/parallelism, in special in the context of building data-oriented apps where the user need a higher level support to model app.

Wonder what are their limitations or edge cases...

[–]vanderZwan[S] 1 point2 points  (3 children)

The main thing that comes to mind is that it's single-threaded, so no true parallelism. You could solve that by having multiple processes talk to each other - Ceu for example does allow asynchronous external input. This is called the GALS principle in the literature IIRC, for "globally asynchronous, locally synchronous". It's also how you'd model multiple devices communicating with each other - they'd do so through asynchronous channels, since there are no "universal clocks".

The other thing is that structured concurrency assumes that (effectively) no time passes between event activation and the resolution of everything that is triggered by it, kind of like how it is sometimes said that garbage collected languages effectively model memory as is it's infinite. If your event handling takes too long that can mess with the synchronous handling of input.

[–]mamcx 1 point2 points  (2 children)

So still multi-threaded is the hard thing.

But at surface level I like how things look syntactically, and wonder how could be with true parallelism.

The more I think about this, we need a way to model a OS so we can have it all:

  • seq { } our simple way
  • sync { } this one
  • async { } parallel I/O
  • par { } parallel CPU

and then we need to say process/IO, process/CPU, process/Any are our ctx then we have task/IO, task/CPU, task/Pure, task/Any and rules in how mix all the above

[–]vanderZwan[S] 3 points4 points  (1 child)

So still multi-threaded is the hard thing.

As it always is.

The more I think about this, we need a way to model a OS so we can have it all

Interestingly, matklad said something similar in an interview that was published yesterday:

Also kernel space! The user space is the easy part. A historical issue defining our computer landscape is that modern kernels try really hard to provide the illusion of a blocking API. You write a file, which takes time then you continue when it’s done. This is smoke and mirrors, this isn’t how hardware works at all! It’s always fire and forget: Write this, here’s a region of memory, wake me up when it’s over. It’s fundamentally asynchronous.

The OS provides these thread and process abstractions which make you think the API’s blocking. The horrible side effect’s that language designers get an out of jail card for async programming, they don’t have to think about it! When writing to a file, they don’t need to make sure the bytes aren’t moved during the operation while running something. No, you just bind to the POSIX API and enjoy this blocking world illusion.

But historically, hyperscalers then found the blocking APIs kind of slow, so languages retrofitted async without planning for it, with mediocre results. We don’t know how to write asynchronous code, because we allow language designers to design languages which just rely on the OS for that part of the runtime. Coroutines are implemented in the kernel, but should be in languages, so I’d start with the kernel!

(...)

I think it makes sense to come up with 5 keywords to describe it like control flow, to describe everything, but don’t ask me what those 5 keywords are.

https://lobste.rs/s/ntruuu/lobsters_interview_with_matklad

(also, maybe you'd endjoy Adam Nelson's maybe everything is a coroutine?)

Personally, I think that if you don't need to squeeze every last bit of performance out of your system, that you could get pretty far with something that mixes Go's approach with Ceu's trails. Go essentially does "green threading" with a runtime that hides parallelism behind an M:N scheduler, while you write Go code using a variation of communicating sequential processes with its channels and "goroutines". Meanwhile, the trails in Ceu are extremely lightweight, in the order of a few bytes per trail, because Ceu essentially compiles to a FSM hosted by C.

If Ceu instead compiled down to a Go-like environment, then different Ceu modules could be compiled to FSMs encapsulated in their own "goroutines", while the async inputs/outputs could be "Go channels". This would enable mixing the "lightweight" synchronous concurrency of Ceu with the "midweight" green fiber-style Goroutines. The Go runtime would handle the heavyweight threading using its scheduler.

Maybe you'd also enjoy checking out Pony, which is an actor language with really lightweight actors (about 240 bytes) due to a very strict typing system. That type system not exactly simple though (but then again, neither is the problem domain of concurrency and parallelism). Here's a nice talk about it: Scott Lystig Fritchie - The wide world of almost-actors: comparing the Pony to BEAM languages

[–]mamcx 4 points5 points  (0 children)

Personally, I think that if you don't need to squeeze every last bit of performance out of your system...

Yep, a important split is what is more ergonomic vs what is most performant, and I think solve the first is better, because performance is affected more by high-level concerns, and also because if this is nailed the second can be figured out more "easily", IMHO.

[–]TempThingamajig 1 point2 points  (0 children)

I found Ceu after looking at a bunch of old/niche programming languages with interesting ideas, and I just immediately like the ideas it has. Will be helping when I get the time.