This repository hosts Chymyst Core
— a library that provides a Scala domain-specific language for declarative concurrency.
Chymyst
is a framework-in-planning that will build upon Chymyst Core
to enable creating concurrent applications declaratively.
Chymyst
is based on the chemical machine paradigm, known in the academic world as Join Calculus (JC).
JC has the same expressive power as CSP (Communicating Sequential Processes) and the Actor model, but is easier to use.
(See also Conceptual overview of concurrency.)
The initial code of Chymyst Core
was based on previous work by Jiansen He (2011) and Philipp Haller (2008), as well as on Join Calculus prototypes in Objective-C/iOS and Java/Android (2012).
The current implementation is tested under Oracle JDK 8 with Scala 2.11.8
and 2.12.1
.
This talk was given at Scalæ by the Bay 2016. See also these talk slides revised for the current syntax.
This is a complete runnable example. The logic of "dining philosophers" is implemented in a completely declarative and straightforward code.
import io.chymyst.jc._
object Main extends App {
/** Print message and wait for a random time interval. */
def wait(message: String): Unit = {
println(message)
Thread.sleep(scala.util.Random.nextInt(20))
}
val hungry1 = m[Int]
val hungry2 = m[Int]
val hungry3 = m[Int]
val hungry4 = m[Int]
val hungry5 = m[Int]
val thinking1 = m[Int]
val thinking2 = m[Int]
val thinking3 = m[Int]
val thinking4 = m[Int]
val thinking5 = m[Int]
val fork12 = m[Unit]
val fork23 = m[Unit]
val fork34 = m[Unit]
val fork45 = m[Unit]
val fork51 = m[Unit]
site (
go { case thinking1(_) => wait("Socrates is thinking"); hungry1() },
go { case thinking2(_) => wait("Confucius is thinking"); hungry2() },
go { case thinking3(_) => wait("Plato is thinking"); hungry3() },
go { case thinking4(_) => wait("Descartes is thinking"); hungry4() },
go { case thinking5(_) => wait("Voltaire is thinking"); hungry5() },
go { case hungry1(_) + fork12(_) + fork51(_) => wait("Socrates is eating"); thinking1() + fork12() + fork51() },
go { case hungry2(_) + fork23(_) + fork12(_) => wait("Confucius is eating"); thinking2() + fork23() + fork12() },
go { case hungry3(_) + fork34(_) + fork23(_) => wait("Plato is eating"); thinking3() + fork34() + fork23() },
go { case hungry4(_) + fork45(_) + fork34(_) => wait("Descartes is eating"); thinking4() + fork45() + fork34() },
go { case hungry5(_) + fork51(_) + fork45(_) => wait("Voltaire is eating"); thinking5() + fork51() + fork45() }
)
// Emit molecules representing the initial state:
thinking1() + thinking2() + thinking3() + thinking4() + thinking5()
fork12() + fork23() + fork34() + fork45() + fork51()
// Now reactions will start and print messages to the console.
}
The Chymyst Core
library is in alpha pre-release, with very few API changes envisioned for the future.
The semantics of the chemical machine (restricted to single-host, multicore computations) is fully implemented and tested on many nontrivial examples.
The library JAR is published to Maven Central.
Extensive tutorial and usage documentation is available.
Unit tests include examples such as concurrent counters, parallel “or”, concurrent merge-sort, and “dining philosophers”. Test coverage is 100% according to codecov.io.
Performance benchmarks indicate that Chymyst Core
can schedule about 10,000 reactions per second per CPU core, and the performance bottleneck is in submitting jobs to threads (a distant second bottleneck is pattern-matching in the internals of the library).
Known limitations:
Chymyst Core
is about 2x slower than Jiansen He'sScalaJoin
on the blocking molecule benchmark, and about 1.2x slower on some non-blocking molecule benchmarks.Chymyst Core
has no fairness with respect to the choice of molecules: If a reaction could proceed with many alternative sets of input molecules, the input molecules are not chosen at random.Chymyst Core
has no distributed execution (Jiansen He'sDisjoin.scala
is not ported toChymyst
, and probably will not be). Distributed computation should be implemented in a better way than posting channel names on an HTTP server. (However,Chymyst Core
will use all cores on a single machine.)
sbt test
The tests will print some error messages and exception stack traces - this is normal, as long as all tests pass.
Some tests are timed and will fail on a slow machine.
sbt benchmark/run
will run the benchmarks.
To build the benchmark application as a self-contained JAR, run
sbt benchmark/assembly
Then run it as
java -jar benchmark/target/scala-2.11/benchmark-assembly-*.jar
Chymyst Core
is published to Maven Central.
To pull the dependency, add this to your build.sbt
at the appropriate place:
libraryDependencies += "io.chymyst" %% "core" % "latest.integration"
To use the chemical machine DSL, add import io.chymyst.jc._
in your Scala sources.
See the "hello, world" project for a complete minimal example.
To build the library JARs:
sbt core/package core/package-doc
This will prepare JAR assemblies as well as their Scaladoc documentation packages.
The main library is in the core
JAR assembly (core/target/scala-2.11/core-*.jar
).
User code should depend on that JAR only.
$ sbt
> project core
> +publishSigned
> sonatypeRelease
This drawing is by Robert Boyle, who was one of the founders of the science of chemistry.
In 1661 he published a treatise titled “The Sceptical Chymyst”, from which the Chymyst
framework borrows its name.