This library implements join patterns in Scala 3, a coordination mechanism for concurrent message-passing programs, first introduced in the join calculus. Join patterns allow to declaratively specify how to react and synchronize distributed computations.
The library offers a convenient and safe method to define and utilize join patterns in Scala 3. It achieves this by leveraging Scala 3's metaprogramming capabilities, extending the language through the use of macros and the reflection API. We use the Scala 3 pattern matching syntax to define join patterns, and the library converts these into an internal representation using the aforementioned techniques.
Additionally, the library uses the Actor model as a practical example to demonstrate the application of join patterns. At present, we are employing a simple homemade actor model implementation.
The formal foundation upon which this library is based can be found in the ECOOP'24 paper. A comprehensive description of the implementation of the library can be found in the Programming Journal 25 paper.
core/- Core join patterns library implementationcore/src/main/scala/join_patterns/- Implementation of join patternscore/src/main/scala/examples/- Usage examples
benchmarks/- Performance benchmarking suite
The library provides a simple API to define join patterns. The following example demonstrates how to define a join pattern for a simple factory shop floor monitoring system, as seen in the paper:
- First add the necessary imports:
import join_actors.api.*- Define the messages that the actors will exchange. We use Scala 3 enums to define the message types with their respective payloads:
enum MachineEvent:
case Fault(faultID: Int, ts: Long)
enum WorkerEvent:
case Fix(faultID: Int, ts: Long)
enum SystemEvent:
case DelayedFault(faultID: Int, ts: Long)
case Shutdown()
type Event = MachineEvent | WorkerEvent | SystemEvent- Now we define the monitor as an actor with join definitions using the
receivemacro. Join patterns are written as message patterns separated by the&:&operator, and followed by a guard condition. The old tuple-like syntax is also supported
def monitor(matcher: MatcherFactory) =
Actor[Event, Unit] {
receive { (self: ActorRef[Event]) =>
{
case Fault(fid1, ts1) &:& Fix(fid2, ts2) if fid1 == fid2 => ...
case Fault(fid1, ts1) &:& Fault(fid2, ts2) &:& Fix(fid3, ts3)
if fid2 == fid3 && ts2 > ts1 + TEN_MIN => ...
case DelayedFault(fid1, ts1) &:& Fix(fid2, ts2) if fid1 == fid2 => ...
...
}
}(matcher)
}- Finally, we can run the monitor as follows:
def runFactorySimple(matcher: MatcherFactory) =
// Predefined sequence of events
val events = List(
Fault(1, ONE_MIN),
Fault(2, TEN_MIN),
Fault(3, QUARTER_HR),
Fix(3, THIRTY_MIN)
)
// Start the monitor actor
val (monitorFut, monitorRef) = monitor(matcher).start()
// Send the events to the monitor actor
events foreach (event => monitorRef ! event)
// Shutdown the monitor actor
monitorRef ! Shutdown()
// Wait for the monitor actor to finish
Await.ready(monitorFut, Duration(15, "m"))The matcher can be set to any MatcherFactory implementations, allowing access to all matchers.
In the example above, some minor details are omitted for brevity. The full
example can be found in the FactorySimpl.scala file.
The library can be compiled by installing a Java Development Kit (version 21 or later) and
sbt (version 1.9 or later) and running sbt compile. Then, sbt will download the required
dependencies (including the Scala 3 compiler).
To compile the library, run the following command from the root directory (where
the build.sbt file is located):
sbt clean compileTo run the tests of the core library, run the following command:
sbt core/testTo run for instance the Factory Simple example with the predefined configuration
run the following command:
sbt "core/run factory-simple --matcher stateful"A Makefile is provided for convenient access to common tasks. Run make help to see all available targets:
make helpThe --matcher flag can be set to any of the following, allowing access to all implemented matchers:
brutestatefulmutablelazy-mutablewhile-lazywhile-eagereager-parallellazy-parallelfiltering-whilefiltering-parallelarray-while
There are other examples available in the examples package that can be run in a similar way.
See the benchmarks README for more information on how to run the benchmarks.
-
Philipp Haller, Ayman Hussein, Hernán Melgratti, Alceste Scalas, and Emilio Tuosto. Fair Join Pattern Matching for Actors. In 38th European Conference on Object-Oriented Programming (ECOOP 2024). Leibniz International Proceedings in Informatics (LIPIcs), Volume 313, pp. 17:1-17:28, Schloss Dagstuhl – Leibniz-Zentrum für Informatik (2024) https://doi.org/10.4230/LIPIcs.ECOOP.2024.17
-
Ayman Hussein, Philipp Haller, Ioannis Karras, Hernán Melgratti, Alceste Scalas, and Emilio Tuosto. ‘JoinActors: A Modular Library for Actors with Join Patterns’. The Art, Science, and Engineering of Programming. 15 February 2026. https://doi.org/10.22152/programming-journal.org/2026/11/4