The Concurnas Programming Language
concurnas.com
concurnas.com
onchange(x){ doSomething()}//on change of x, perform an action
every(x){ doSomething(x)}//as above but including initial value
await(x; x > 2)//pause execution until the condition is met
trans{//a transaction acting upon two references
a -= 10
b += 10
}
z <= a+b//shorthand for every
z <- a+b//shorthand for onchange
Is this novel to abstract languages? Would something like this work in a language without a VM like C++?I imagine needing registrations of these conditionals in the VM to watch and execute.
As far as languages go, the ideas you showed look similar to how Esterel[3] works, or perhaps Occam[4].
[2] https://github.com/hoplon/javelin
[3] https://en.wikipedia.org/wiki/Esterel
[4] https://en.wikipedia.org/wiki/Occam_(programming_language)
For background reading in this area, perhaps start with "A poor man's concurrency Monad" by Claessen, it's a very easy read and shows how green threads can be written as a user library allowing pluggable schedulers. F# also implements its asynchronous workflows using essentially this technique, see "The F# Asynchronous Programming Model". LINQ and the reactive library Rx from C# are also great examples of Monads.
I have to register the fact that it drives me crazy to not have spaces before and after comment //s
As far as distributed computing is concerned, here the error landscape is much larger than with single machine computation (network disconnections etc). The distributed computing component of Concurnas allows one to define custom error handlers which can deal with errors occurring specifically from distributed computing, and there are a couple of pre-defined error handlers which can be used to assist here (fail on first incident and retry up to x times): https://concurnas.com/docs/distComp.html#common-error-handli...
The documentation about distributed computing (and distributed error handling in particular) could be better written. I will look to make that a priority now that you've raised this point.
However I feel like at least the landing page dodges a lot of hard questions. For example the biggest reason anyone should want to write a concurrent program over a synchronous single-threaded one is performance, so talking about how your features are implemented efficiently is important. Specific examples:
Having my isolates semantically copy all their inputs sure might avoid race conditions, but I hope that there's some fancy copy-on-write and sub-slicing stuff going on so that if I spawn 10 threads to process 10 parts of my array I don't make 10 copies of the whole array. For GPU support I want to
The off-heap support mentions "all objects are quickly serializable and deserializable to and from this format by default". Anyone can claim "quickly", I want to know exactly how efficient this serialization is. Does it use zero-copy casting with some patching? Is it just a memcpy to the GC heap? Does each type get code-generated serializers/deserializers or maybe it's done with reflection? Whether this feature is useful entirely depends on exactly how fast this serialization is.
How does the remote code execution transmit code? Do I need to make sure that my work units are big enough that every remote JVM can warm up and JIT transmitted bytecode? Is there caching so I don't transmit the standard library every time?
The other hard problem an HPC language needs to handle is error handling. What happens when a processor goes down, data isn't received in a timely fashion, etc. How a data processing job might get rerouted to a different server and how intermediate data calculations might get checkpointed so the loss of one result doesn't mean redoing the whole computation.
I don't agree with this sentiment. Concurrency is a great abstraction, even if not run in parallel. After using CSP (in Go) for a time I don't care for other (mainstream) languages as much due to their lack of good concurrency primitives. I love writing programs as a bunch of services that work together. Feels like shell/pipe scripting or having daemons work together over sockets. Fits my brain very well.
So tired of leading blurbs like this
You are making a lot of claims and assumptions here. A much better way is to think about what you actually are trying to accomplish. Futures and coroutines aren't going to save anyone either.
Futures are a simplified tool for the larger concept of executing asynchronously and synchronizing the results. The generalization is that you will need to figure out what data you need to package together for each unit of execution so that they can be used without dependencies. The dependencies of different units will have to be figured out so they can run when they have what they need. Futures are the same as having only one thing to execute or at best, a single chain that doesn't split or converge.
Coroutines are a way to hide the fact that you need to keep some state in between executing, just like recursive functions hide the fact that you are using the call stack as a stack data structure.
Confronting the dependencies and IO of each unit of execution as well as understanding where you need to keep state and where+how you will synchronize that data created asynchronously is what gets you concurrency and parallelism that makes sense as it scales. This does not require a different language, it just requires an architecture that does the tricky stuff for you and maps directly to what actually needs to happen.
...and waddayaknow, every language people actually use has that built in. Self-refutation at its finest.
> where+how you will synchronize that data
Using what primitives? Oh yeah, more library cruft that would have been more concise and less error-prone than if the language gave direct support.
> an architecture that does the tricky stuff for you
What's the difference between tying yourself to a framework (what the context makes clear you're really talking about) that solves the tricky problems for you vs. a language that does? As soon as you're relying on somebody else to solve the tricky problems, you're letting them define the language in which you express your own logic - even if you don't think of it as such. And this is where I'd generalize Greenspun's tenth rule to say that any ad-hoc informally-specified implicit language is almost surely inferior to a real language designed for the same purpose.
The point was never that people don't use coroutines or recursion. It is that neither solves fundamental problems and are closer to syntax sugar in that are something that people can reach for instead of doing what they want directly.
> Using what primitives? Oh yeah, more library cruft that would have been more concise and less error-prone than if the language gave direct support.
If you synchronize using data structures, that would mean you aren't using primitives.
> Oh yeah, more library cruft that would have been more concise and less error-prone than if the language gave direct support.
Why would that be true? Lock free hash maps and queues can fit in single file zero dependency headers. You think that's cruft? What about an entire language to solve something you can do without tying yourself to a bare bones ecosystem that no one knows.
> As soon as you're relying on somebody else to solve the tricky problems, you're letting them define the language in which you express your own logic
Meaning you want to not use a different language and solve all your concurrency yourself? The whole idea is to not keep solving concurrency in ad hoc ways. Also guess what, the moment you step in to a new language, you are going to be solving a _TON_ of old problems yourself.
I am not sure why you are so upset and invested in this. You seem to be arguing against yourself with respect to your conclusions and the flimsy rationalizations for them. These aren't actually problems that take a huge number of lines to solve, they just have to be done well. Throwing out everything from syntax highlighting and completion to debugging, to IDE support to libraries written in the same language is always a painful experience. Including a few files for data structures and task graphs is much less so.
Before you dismiss the paper based on the title, let me correct it and say it really should be "Threads should not be implemented as a library." Boehm's argument is that when you implement something like threads as a library, and the language itself is not aware of the semantics, lots of bad stuff happens.
This is about how to handle parallelism and concurrency, which goes far beyond threads.
Just as working with raw pointers and no data structures with no wrappers for length or ownership is error prone, using threads by themselves doesn't scale to complex use cases without becoming intractable at a certain point, so there needs to be structure.
Most programmers will never be able to do more than trivial threading (if that) so the difficult parts needs to separated into something that is much easier but still useful, but using a new language is incredibly misguided because you give up an enormous amount without gaining anything non trivial. A language might be able to give an ironclad guarantee that a function running concurrently doesn't mutate anything, but in practice this is not hard to avoid. The difficulty comes in data structures and dependencies, because that is ultimately where the synchronization is.
Yes, I agree. But I think it is useful to try to apply the lesson of that paper further: capabilities that we want to give programmers that change the semantics of their code in a fundamental way maybe should be part of the language. I think it's a sliding scale. Your position seems to be "if we can do it in a library, we don't need a language." I'm proposing that there is a spectrum.
> change the semantics of their code in a fundamental way
What is this semantic change you are talking about? Threads run what you give them, they don't change the semantics. Synchronization primitives are already in languages. Using the synchronization primitives to get something fast yet simple to use is the tricky part, and that is something that can be done with simple pieces and relatively small data structures.