### Write You An Actor (System) For Great Good! with JBang, JDK 19, records, pattern matching and virtual threads!
Andrea Peruffo
@and_prf
Edoardo Vacchi
@evacchi
evacchi.github.io
### Software Requirements
In order to do this workshop, you need the following: https://bit.ly/actor19 1. Linux or MacOS with shell access, and `git` installed 2. [`jbang`](https://www.jbang.dev/documentation/guide/latest/installation.html) 3.
`git clone git@github.com:strehler/workshop-jbcn.git`
4. (optionally) JDK 19
Suggested JDK 19 installation with [`sdkman`](https://sdkman.io/) ```bash $ sdk install java 19.ea.31 $ sdk use java 19.ea.31 $ java -version | openjdk version "19-ea" 2022-09-20 | OpenJDK Runtime Environment (build 19-ea+31-2203) | OpenJDK 64-Bit Server VM (build 19-ea+31-2203, mixed mode, sharing) ```
### JBang Unprecedented Java experience at your fingertips: ```bash $ jbang init hello.java $ ./hello.java | [jbang] Building jar... | Hello World ```
### DIY hello.java - initialize a script: ```bash jbang init hello.java ``` - (optional) open it in an editor: ```bash jbang edit --open=[editor] hello.java ``` - configure `jbang` by adding from the second line: ``` //JAVA 19 //JAVAC_OPTIONS --enable-preview --release 19 //JAVA_OPTIONS --enable-preview ``` - (optional) use Java 19 idioms - run it!
### Goals
### What's new with the JDK? - JDK 17 is the LTS - You should not wait to upgrade - A walkthrough of the latest changes in JDK 17 - Bonus: a sneak peek in what is possible with JDK 19
### Finding a Running Example - An example to exercise brand new JDK features - In a fun way - You will learn all the new features - ...with a nontrivial example (an actor runtime!)
### The Actor Model
```java sealed interface Pong {} record SimplePong(Address
sender, int count) implements Pong {} record DeadlyPong(Address
sender) implements Pong {} record Ping(Address
sender, int count) {} static void main(String... args) { var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); Address
ponger = actorSystem.actorOf(self -> msg -> pongerBehavior(self, msg)); Address
pinger = actorSystem.actorOf(self -> msg -> pingerBehavior(self, msg)); ponger.tell(new Ping(pinger, 10)); } static Effect
pongerBehavior(Address
self, Ping msg) { if (msg.count() > 0) { out.println("ping! 👉"); msg.sender().tell(new SimplePong(self, msg.count() - 1)); return Stay(); } else { out.println("ping! 💀"); msg.sender().tell(new DeadlyPong(self)); return Die(); } } static Effect
pingerBehavior(Address
self, Pong msg) { return switch (msg) { case SimplePong(var sender, var count) -> { out.println("pong! 👈"); sender.tell(new Ping(self, count - 1)); yield Stay(); } case DeadlyPong p -> { out.println("pong! 😵"); yield Die(); } }; }
### The Actor Model - Concurrency Model - Simple - "Single-Thread" reasoning - Message-based, "Mailbox"
### The Actor Model - Modelling stateful systems - Distributed systems (e.g. leveraging location transparency) - IoT (e.g. Digital twins) - ...
An [actor](https://en.wikipedia.org/wiki/Actor_model) is a **computational entity** that, *in response to a message* it receives, can concurrently: - **send** a finite number of messages to other actors; - **create** a finite number of new actors; - **designate the behavior** to be used for the next message it receives. "Everything" is an actor.
### Erlang ```erlang pingponger(Name, Main) -> receive {ping, Count, Ping_PID} -> io:format("~s received ping, count down ~w~n", [Name, Count]), if Count > 0 -> Ping_PID ! {ping, Count - 1, self()}, pingponger(Name, Main); true -> exit ("done") end end. main(_) -> Ping1_PID = spawn(fun() -> pingponger("pinger", self()) end), Ping2_PID = spawn(fun() -> pingponger("ponger", self()) end), Ping1_PID ! {ping, 10, Ping2_PID}. ```
### Elixir ```elixir defmodule Main do def pingponger(name) do receive do {:ping, count, sender} -> IO.puts "#{name} received ping, count down #{count}" if count > 0 do send sender, {:ping, count - 1, self()} pingponger(name) else :ok end end end end pid1 = spawn fn -> Main.pingponger("pinger") end pid2 = spawn fn -> Main.pingponger("ponger") end send pid1, {:ping, 10, pid2} ```
### Scala / Akka untyped ```scala case class Ping(count: Int) class Pingponger extends Actor { def receive = { // Any => Unit case Ping(count) => println(s"${self.path} received ping, count down $count") if (count > 0) { sender() ! Ping(count - 1) } else { System.exit(0) } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping(10), ponger) ```
### Scala / Akka typed ```scala case class Ping(count: Int, replyTo: ActorRef[Ping]) object Pingponger { def apply(): Behavior[Ping] = Behaviors.receive { (context, ping) => println(s"${context.self.path} received ping, count down ${ping.count}") if (ping.count > 0) { ping.replyTo ! Ping(ping.count - 1, context.self) } else { System.exit(0) } Behaviors.same } } object Main { def apply(): Behavior[NotUsed] = Behaviors.setup { context => val pinger = context.spawn(Pingponger(), "pinger") val ponger = context.spawn(Pingponger(), "ponger") pinger ! Ping(10, ponger) Behaviors.empty } } ActorSystem(Main(), "PingPongDemo") ```
### Java + Akka Protocol ```java class Ping { final int count; ActorRef
replyTo; Ping(int count, ActorRef
replyTo) { this.count = count; this.replyTo = replyTo; } int getCount() { return count; } ActorRef
getReplyTo() { return replyTo; } } ```
### Java + Akka Implementation ```java static class Pingponger extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .match( Ping.class, ping -> { out.println(getSelf().path() + " received ping, count down " + ping.getCount()); if (ping.getCount() > 0) { getSender().tell(new Ping(ping.getCount() - 1), getSelf()); } else { System.exit(0); } }) .build(); } } public static void main(String... args) { ActorSystem system = ActorSystem.apply("pingpong"); ActorRef pinger = system.actorOf(Props.create(Pingponger.class), "pinger"); ActorRef ponger = system.actorOf(Props.create(Pingponger.class), "ponger"); pinger.tell(new Ping(10), ponger); } ```
### Java + Akka ```java @Override public Receive
createReceive() { return newReceiveBuilder() .onMessage(Greet.class, this::onGreet) .build(); } ``` https://doc.akka.io/docs/akka/2.4/java/untyped-actors.html
### Akka - clunky to write in pre-17: - no concise way to express messages - no tidy syntax to match against the types of the incoming messages - no tidy way to represent closed type hierarchies
### Java 17+ - records - switch expressions and pattern matching - sealed type hierarchies (exhaustiveness check).
### Bonus Java 19 - record patterns (destructuring match) - virtual threads!
### Java 17+
### Records ``` record User(String name) {} ```
### Sealed Type Hierarchies ``` sealed interface GreetingTarget {}; record User(String name) implements GreetingTarget {}; record World() implements GreetingTarget {}; ```
### Pattern matching (JDK 17) ``` GreetingTarget tgt = ... switch (tgt) { case World w -> out.println("Hello World"); case User u && u.name() == "..." -> out.println("Hello " + u.name()); } ```
### Pattern matching (JDK 19) ``` GreetingTarget tgt = ... switch (tgt) { case World w -> out.println("Hello World"); case User(var name) when (name == "...") -> out.println("Hello " + name); } ```
### Let's do some coding!
### Behavior ```java void receive(Message m) { } ```
### Behavior Message → NextState
### Behavior Message → Effect
### State Transition State → State
### Behavior Transition Behavior → Behavior
### Effect Behavior → Behavior
# LIVE CODING

# END