> Effect handlers let you define advanced control abstractions, like exceptions, async/await, iterators, parsers, ambient state, or probabilistic programs, as a user library in a typed and composable way.
> Perceus is an advanced compilation method for reference counting. This lets Koka compile directly to C code without needing a garbage collector or runtime system! This also gives Koka excellent performance in practice.
Effectful functional language that compiles to C? Sounds great.
I’ve been following the road to OCaml 5 and the effect system is nice, but getting to have effects without bringing in a garbage collector seems quite special. I’m working on a C-WASM wrapper library, and one issue I struggle with in C code is dealing with async IO outside the WASM module. I haven’t looked deep into the FFI story of this new language but it would directly solve this problem that currently needs the ASYNCIFY Binaryen transform which adds a 2x size increase and 50% performance penalty. Can Koka call C in an effect? Can it suspend a stack if C calls into it? I can presumably drop koka into my existing C-WASM project with a few new Makefile rules. These are definitely places OCaml can’t go; by compiling to C Koka makes these advanced FP features much more attainable (at least, for me).
Koka is garbage collected. There are several terminology ambiguities in play: the documentation takes "garbage collection" to refer to tracing garbage collection as opposed to runtime cost of any sort, which while common in some circles seems less so common as a whole (this is extremely confusing, all the time). This runtime overhead is reference counting and so is going to be a whole lot nicer to deal with (especially re: C FFI) than tracing, but it does exist.
The reason Koka's GC is interesting despite being based on reference counting is that its ownership system eliminates most of these reference checks at compile time - and additionally can tell whether to use atomic RC (slow but threadsafe for shared data) or non-atomic RC (fast but only threadsafe when data is moved across threads, not shared). This ownership analysis is very similar to what some other languages like Nim do (except Nim differs in not allowing atomic RC at all).
The other strange terminology that is occasionally tossed around in Koka documentation is "garbage free": Koka takes this to mean that at any given point in the program, there is no memory waiting to be freed. This is because the ownership analysis lets the compiler know exactly where the last use (or possible last use) is and insert destructors accordingly. All of that has made Koka's GC algorithm fast and low-overhead enough that it's competitive with state-of-the-art tracing GCs (specifically, OCaml's GC). I haven't seen benchmarks comparing it to manual memory management or strict ownership systems but that's not terribly the point - manual memory management is unsafe and strict ownership is complicated + inexpressive on occasion. Koka's system might just be the best you can get, with those tradeoffs in mind.
Anyway, this doesn't answer your question at all. Sorry. I hope it's interesting, though.
I’m on their side of this semantics issue - I don’t consider reference counting to be garbage collection, to me it’s a stand alone group of memory management techniques, even if there is some overlap in implementation techniques with (tracing) GC sometimes. It’s in the same category as Swift, right? No runtime, different thing.
Well, it has a runtime. Checking and updating the reference count (more so the latter) is not zero-cost. This runtime is just deterministic. Of course, then do we have the same definition of runtime (I would take it to mean any extra memory or processor overhead at runtime that is not strictly necessary)... naming and consistent naming is an extremely hard problem in computer science.
It's in the same category as Swift, yes, but much improved: Swift does not do ownership analysis to get rid of counts (though I've heard they're looking at alternative region-based approaches), and their counts across threads are always atomic (and thus slow).
Reference counting has traditionally led to worse performance than tracing. So even though I get the desire to think of it as separate because it's just transparently replacing your allocator / deallocator with one that does a little bit more instead of having a whole separate tracing collector program, I'd still probably refer to both tracing and reference counting as "garbage collection", and then refer to them + ownership systems (+ regions + everything else new) as "memory management techniques".
Interesting project. Koka does have great interop with C code. An async library has existed in the past, and will likely be reintegrated soon. Unfortunately that will most likely be backed by the LibUV event loop which is not WASM compatible. However, Koka's delimited continuation machinery does not require stack suspension or stack swapping at all. All it requires is a bit of thread local state. Koka builds up continuations through a series of function pointers and closure state, and only when needed. So theoretically all you need is some sort of event loop.
Also, as a Lisp programmer, I absolutely loved reading the section at https://koka-lang.github.io/koka/doc/book.html#sec-handlers - it's essentially like using the Common Lisp condition system, making me feel at home, except it's more generalized (e.g. masking individual condition handlers is not generally possible in CL) and also working in a strongly statically typed environment.
As far as I understand the PL theory involved, it's statically-typed delimited continuation, which I think is even more general than the CL condition system.
In CL, you can control the visibility of restarts. In the restart-bind construct, there is a :test-function which serves as a predicate that determines whether the restart is visible. (Unfortunately, this function is referred to as a condition).
While restarts can be visible or not, I believe there is no such mechanism for handlers. However, handlers can be effectively invisible/transparent by declining to handle a condition, which they can do simply by returning instead of perpetrating a non-local transfer.
Also, there doesn't seem to be an API in Common Lisp for calculating the handlers visible at a given point for a condition of a given type. So that means that the ability of a handler to decline a condition is pretty much as good as a visibility mechanism.
> Perceus is an advanced compilation method for reference counting. This lets Koka compile directly to C code without needing a garbage collector or runtime system! This also gives Koka excellent performance in practice.
Effectful functional language that compiles to C? Sounds great.