The problem of effects in Rust (2020)
without.boats
without.boats
* Pony - capabilities based language inspired by `E`. Effects are like Rust's (provided by the language, not extensible by end users): https://www.ponylang.io/
* Koka - effects are first class and higher order. `filter` is a higher order function that is "effectfully parameterized" as well as "parameterized over its types". Effects can be discharged at any level of the program and the effects "stack" is manipulable (in what appears to be a principled way) https://koka-lang.github.io/koka/doc/index.html
* Multi-core OCaml is also building parallelism on a version of effects that looks quite a lot like Koka (though I'm not familiar enough with it to say how close they are in practice).
What other ones am I missing?
(edit) c'n'p from their site:
Why Flix?
Flix aims to offer a unique combination of features that no other programming language offers, including: algebraic data types and pattern matching (like Haskell, OCaml), extensible records (like Elm), type classes (like Haskell, Rust), higher-kinded types (like Haskell), local type inference (like Haskell, OCaml), channel and process-based concurrency (like Go), a polymorphic effect system (unique feature), first-class Datalog constraints (unique feature), and compilation to JVM bytecode (like Scala).
There is also a former thread on HN: https://news.ycombinator.com/item?id=25513397
[0] https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax
But this list actually makes me excited!
(edit) you can import Java, Kotlin and Scala in it too, those are automatically Impure. Would enable purely functional architecture [1] and falling into the "Pit of success" [2].
[0] https://flix.dev/research/ [1] https://blog.ploeh.dk/2016/03/18/functional-architecture-is-... [2] https://www.youtube.com/watch?v=US8QG9I1XW0
[0]: https://github.com/nicuveo/ghc-proposals/blob/master/proposa...
They use "abilities" as a dual to effects. That's imho the crucial part. Most of the other effect systems only implement half the thing.
But I would change two parts: First of all the co-effects should be proper "capabilities" in the sense of "capability security". The other thing would be modelling the co-effects as co-data. They already use some (ad hoc?) notion of "Interfaces". Those look like co-data definitions (resembling OOP Interfaces). I also thing co-data with co-pattern matching could solve some of the problems mentioned at the end of the paper. Inspiration could be taken form: https://www.cs.uoregon.edu/Reports/DRP-201905-Sullivan.pdf
"Do, be, do, be, do" (those who read it will understand)
This paper really opened my eyes for what's possible.
That being said, Rust has a bit of a harder time due to the focus on performance and optimally zero-cost abstraction, whereas other languages are more lenient and focus rather on expressiveness and composability / code reuse.
I can very easily have a fallible, asynchronous, multi-value, stateful, database connected computation that returns a Bool and use it in let’s say ‘filterM’.
Most transformers are already written for me.
Edit: switching between no effect and some effect is the tricky part though.
(Just to add- there's a really wide range of ergonomics that you can support and it's hard to get everything right. And performance is super tricky!)
Maybe also ATS, but I'm unsure if it qualifies
[1]: https://www.janestreet.com/tech-talks/effective-programming/
Last I read, they really want to nail down the entire multi-threaded environment to the last detail first (which mostly means make sure no parallel bugs are introduced + single-threaded code won't become slower; both pretty huge goals).
* Panic rewinding. I am not sure how to ensure your Rust function being panic safe. It is quite easy to cause soundness issue if some invariants no longer hold due to panic. I see `PanicGuard` sometimes used in Rust std library.
* Future cancellation. `tokio::select` is one of the infamous examples, where it is quite easy to introduce bug if the future cannot handle cancellation gracefully.
When trying to handle them properly, it feels more like writing traditional C code than Rust.
You could use the linking trick in which your panic handler uses non-existent extern fn. For example, this approach is used in the no-panic crate. Of course, this approach is nothing more than a clever hack with several significant limitations.
>Future cancellation
I would say it's a more general problem of Rust lacking linear types.
Yes out of memory errors should be able to be handled. You might not care when writing web backend slop designed to run under orchestrators but for systems code it is necessary sometimes and Rust is a systems language.
It’s possible to work around both shortcomings but they contradict the languages mission and are warts. Async is a big “excessive cleverness” mistake.
They were considered and even originally built that way, but empirically it was worse.
I got really far with stateful, back in 2016 [1]. Stateful was an attempt to write a coroutine library in a proc macro, which generated state machines, as opposed to using os primitives like green threading. This was back before the rust community really started working in this space. I ended up extracting the type system from rustc to do much of the analysis, but it ultimately failed due to how difficult it was to output rust code that respected the borrow checker rules. I also didn’t have anything like the pinning system, so I couldn’t catch move issues either.
It was a much better idea to just implement this in the compiler.
[1]: http://erickt.github.io/blog/2016/01/27/stateful-in-progress...
Personally I just loathe async in general. Go has it right, but I understand why you can’t do that in Rust. Async is an ugly workaround for the inefficiency of OS threads, and I wish they would just fix that so we can stop all this madness.
Whenever Rust adds new features, C users panic "it's going to end up like C++!", but at the same time from Haskellers' perspective Rust's type system is still not advanced enough.
As the article explains, it's incredibly hard to add these high-level features while still preserving "portable assembly" aspect of the language.
The dogmatic C partisans who insist on building houses out of sand with tweezers will never like it.
It’s basically C++ rebooted with ideas from Haskell, Go, and other modern languages. If Rust completely displaced C++ that would be a huge win for productivity and security / reliability.
Overengineering is the great plague of modern software.
Rust contains a little bit of needless cleverness but most of its complexity is essential to the problem domain of low level systems programming and intrinsic safety. Go operates at a different level with a fat library (almost a VM) and garbage collection, allowing for a very simple type system. Both languages are modern but fill different niches.
Rust is new C++ while Go is closer to Python or early pre-complexity-orgy Java.