Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Write You An Actor (System) For Great Good!

Write You An Actor (System) For Great Good!

Edoardo Vacchi

March 27, 2023
Tweet

More Decks by Edoardo Vacchi

Other Decks in Programming

Transcript

  1. Write You an Actor (System) for Great Good! (with JBang,

    JDK 19, records, pattern matching and virtual threads!)
  2. @evacchi About Me Edoardo Vacchi @evacchi • PL/DSL Research @

    UniMi • R&D @ UniCredit Bank • Drools, Kogito @ Red Hat
  3. package io.github.evacchi; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import static

    java.lang.System.out; public interface Actor { interface Behavior extends Function<Object, Effect> {} interface Effect extends Function<Behavior, Behavior> {} interface Address { Address tell(Object msg); } static Effect Become(Behavior like) { return old -> like; } static Effect Stay = old -> old; static Effect Die = Become(msg -> { out.println("Dropping msg [" + msg + "] due to severe case of death."); return Stay; }); record System(ExecutorService executorService) { public Address actorOf(Function<Address, Behavior> initial) { abstract class AtomicRunnableAddress implements Address, Runnable { final AtomicInteger on = new AtomicInteger(0); } var addr = new AtomicRunnableAddress() { final ConcurrentLinkedQueue<Object> mb = new ConcurrentLinkedQueue<>(); Behavior behavior = m -> (m instanceof Address self) ? Become(initial.apply(self)) : Stay; public Address tell(Object msg) { mb.offer(msg); async(); return this; } public void run() { try { if (on.get() == 1) { var m = mb.poll(); if (m!=null) { behavior = behavior.apply(m).apply(behavior); } }} finally { on.set(0); async(); }} void async() { if (!mb.isEmpty() && on.compareAndSet(0, 1)) { try { executorService.execute(this); } catch (Throwable t) { on.set(0); throw t; }}} }; return addr.tell(addr); } } } evacchi.github.io
  4. @evacchi What You Will Bring Home • Learn new Java

    19 features • Learn an interesting concurrency model • Learn how to run code snippets with JBang
  5. @evacchi What You Will Bring Home • Learn new Java

    19 features • Learn an interesting concurrency model • Learn how to run code snippets with JBang • Learn how to write concise Java program with tricks that will make your coworkers mad
  6. @evacchi Agenda • What is an actor? • Comparison between

    actor runtimes • Features of Java we will be using ☕ • Examples of actors (live coding) • An actor runtime (live coding) • A typed actor runtime (live coding) ☕ • An actor-based chat (live coding) • Virtual Threads and Project Loom • A Loom-based chat (live coding) Live coding with
  7. @evacchi Actor Definition An actor 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.
  8. @evacchi Actor Model • Modelling stateful systems • Distributed systems

    (e.g. leveraging location transparency) • IoT (e.g. Digital twins)
  9. @evacchi Messages vs Methods • you send a message to

    an actor • you invoke a method on an object • however in Smalltalk you send "messages" to objects
  10. @evacchi Messages vs Methods • you send a message to

    an actor • you invoke a method on an object • however in Smalltalk you send "messages" to objects • in fact, some people think that actors are "proper" OOP as envisioned by Alan Kay
  11. @evacchi When Scala Roamed The World • Java 8 in

    2014 • Context: JDK 5/6/7 ◦ Mainstream statically typed OOP programming languages were verbose (C++/Java/C#) ◦ Dynamic languages were more concise (Python/Ruby/Groovy) • Enter Scala ◦ Static typing ◦ Dynamic feel / conciseness ◦ Actor library
  12. @evacchi When Scala Roamed The World • Context: JDK 5/6/7

    ◦ Mainstream statically typed OOP programming languages were verbose (C++/Java/C#) ◦ Dynamic languages were more concise Python/Ruby/Groovy • Enter Scala ◦ Static typing ◦ Dynamic feel / conciseness ◦ Actor library
  13. @evacchi Akka (Scala) case object Ping class Pingponger extends Actor

    { var count = 10 def receive: Any => Unit = { case Ping => println(s"${self.path} received ping, count down $count") if (this.count > 0) { this.count = this.count - 1 sender() ! Ping } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping, ponger)
  14. @evacchi Akka (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) } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping(10), ponger)
  15. @evacchi Akka (Scala) case class Ping(count: Int) class Pingponger extends

    Actor { def receive(msg: Any): Unit = msg match { case Ping(count) => println(s"${self.path} received ping, count down $count") if (count > 0) { sender() ! Ping(count - 1) } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping(10), ponger)
  16. @evacchi Akka + Java public class MyUntypedActor extends UntypedActor {

    LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { if (message instanceof Ping) { Ping p = (Ping) message; if (p.count() > 0) { log.info("Received ping : " + p.count()); getSender().tell(new Ping(p.count() - 1), getSelf()); } } }
  17. @evacchi 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}.
  18. @evacchi 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}
  19. @evacchi Akka + Java public class MyUntypedActor extends UntypedActor {

    LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { if (message instanceof Ping) { Ping p = (Ping) message; if (p.count() > 0) { log.info("Received ping : " + p.count()); getSender().tell(new Ping(p.count() - 1), getSelf()); } } } Can we improve over this?
  20. @evacchi (Ab)using Language Defaults To Reduce Noise public class Main

    { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }
  21. @evacchi (Ab)using Language Defaults To Reduce Noise public class Main

    { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }
  22. @evacchi (Ab)using Language Defaults To Reduce Noise public interface Main

    { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }
  23. @evacchi (Ab)using Language Defaults To Reduce Noise public interface Main

    { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }
  24. @evacchi (Ab)using Language Defaults To Reduce Noise import static java.lang.System.*;

    public interface Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }
  25. @evacchi (Ab)using Language Defaults To Reduce Noise import static java.lang.System.*;

    interface Main { String HELLO = "hello world"; static void main(String... args) { out.println(HELLO); } }
  26. public class HelloWorld { public static void main(String[] args) {

    System.out.println("Hello World"); } } @evacchi
  27. public class HelloWorld { public static void main(String[] args) {

    System.out.println("Hello World"); } } @evacchi
  28. public class HelloWorld { public static void main(String[] args) {

    System.out.println("Hello World"); } } void main() { println("Hello World"); } @evacchi
  29. @evacchi More “Code Golfing” public class Auth { private final

    String login; private final String token; public Auth(String login, String token) { this.login = login; this.token = token; } public String login() { return this.login; } public String token() { return this.token; } public boolean equals(Object o) { ... } public int hashCode() { return Objects.hash(login, token); } public String toString() { ... } } public class Anon { private final String email; public Anon(String email) { this.email = email; } public String email() { return this.email; } public boolean equals(Object o) { ... } public int hashCode() { ... } public String toString() { ... } }
  30. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { if (message instanceof Auth) { Auth a = (Auth) message; check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon) { Anon a = (Anon) message; // grant guest access } else unhandled(message); }
  31. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { if (message instanceof Auth) { Auth a = (Auth) message; check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon) { Anon a = (Anon) message; // grant guest access } else unhandled(message); }
  32. @evacchi Enhanced instanceof public void onReceive(Object message) throws Exception {

    if (message instanceof Auth a) { check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon a) { // grant guest access } else unhandled(message); } pattern matching
  33. @evacchi More “Code Golfing” public class Auth { private final

    String login; private final String token; public Auth(String login, String token) { this.login = login; this.token = token; } public String login() { return this.login; } public String token() { return this.token; } public boolean equals(Object o) { ... } public int hashCode() { return Objects.hash(login, token); } public String toString() { ... } } public class Anon { private final String email; public Anon(String email) { this.email = email; } public String email() { return this.email; } public boolean equals(Object o) { ... } public int hashCode() { ... } public String toString() { ... } }
  34. @evacchi Enhanced instanceof public void onReceive(Object message) throws Exception {

    if (message instanceof Auth(var login, var token)) { check(login); if (validate(login, token)) { // grant full access } else { // error } } else if (message instanceof Anon(String email)) { // grant guest access } else unhandled(message); } deconstruction patterns
  35. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } }
  36. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } Improved Switch
  37. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } Records
  38. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } deconstruction patterns
  39. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } var pattern
  40. @evacchi More “Code Golfing” public void onReceive(Object message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } }
  41. @evacchi Sealed Hierarchies sealed interface LoginInfo {} public record Auth(String

    login, String token) implements LoginInfo {} public record Anon(String email) implements LoginInfo {}
  42. @evacchi Exhaustive Pattern Matching public void onReceive(LoginInfo message) throws Exception

    { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; } } Sealed Type Hierarchy No Default Case!
  43. @evacchi Scala def onReceive(Any: message): Unit { message match {

    case Auth(login, token) => { check(login) if (validate(login, token)) { // grant full access } else { // error } } case Anon(email) => { /* guest access */ }; } }
  44. @evacchi Akka + Java + Pattern Matching public class MyUntypedActor

    extends UntypedActor { LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { switch (message) { case String m -> { log.info("Received String message: {}", message); getSender().tell(message, getSelf()); } default -> unhandled(message); } } }
  45. @evacchi Akka + 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()); } }) .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); }
  46. @evacchi Agenda • What is an actor? • Comparison between

    actor runtimes • Features of Java we will be using ☕ • Examples of actors (live coding) • An actor runtime (live coding) • A typed actor runtime (live coding) ☕ • An actor-based chat (live coding) • Virtual Threads and Project Loom • A Loom-based chat (live coding) Live coding with
  47. @evacchi Closures vs Classes The venerable master Qc Na was

    walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said “Master, I have heard that objects are a very good thing - is this true?” Qc Na looked pityingly at his student and replied…
  48. @evacchi Closures vs Classes Chastised, Anton took his leave from

    his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.
  49. @evacchi Closures vs Classes On his next walk with Qc

    Na, Anton attempted to impress his master by saying “Master, I have diligently studied the matter, and now understand that objects are truly a poor man’s closures.” Qc Na responded by hitting Anton with his stick, saying…
  50. @evacchi Closures vs Classes At that moment, Anton became enlightened.

    http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html
  51. @evacchi Agenda • What is an actor? • Comparison between

    actor runtimes • Features of Java we will be using ☕ • Examples of actors (live coding) • An actor runtime (live coding) • A typed actor runtime (live coding) ☕ • An actor-based chat (live coding) • Virtual Threads and Project Loom • A Loom-based chat (live coding) Live coding with
  52. @evacchi Green Threads • in the Java 1.0 days, some

    JVMs "green" (user-mode) threads. • The green threads of the 90s still had large, monolithic stacks. • A product of their time ◦ systems were single-core ◦ OSes did not have thread support at all. https://www.infoq.com/articles/java-virtual-threads/
  53. @evacchi Virtual Threads • Virtual threads: only superficial similarity to

    Green Threads • More in common with user-mode threads in Go or Erlang • However: semantically identical to java.lang.Thread
  54. @evacchi Virtual Threads • Virtual threads: only superficial similarity to

    Green Threads • More in common with user-mode threads in Go or Erlang • However: semantically identical to java.lang.Thread
  55. @evacchi Green Threads • Solaris 2.6 software • the green

    threads library was user-level • the Solaris system could process only one green thread at a time, • Solaris handled the Java runtime as a many-to-one threading implementation • Java applications could not interoperate with existing MT applications in the Solaris environment. • Java threads could not run in parallel on multiprocessors. • An MT Java application could not harness true OS concurrency for faster applications on either uniprocessors or multiprocessors. Green threads library was replaced with native Solaris threads for Java on the Solaris 2.6 platform; this is carried forward on the Solaris 7 and Solaris 8 platforms. https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqe/
  56. @evacchi Loom https://wiki.openjdk.org/display/loom/Main “Project Loom is intended to explore, incubate

    and deliver Java VM features and APIs built on top of them for the purpose of supporting easy-to-use, high-throughput lightweight concurrency and new programming models on the Java platform.”
  57. @evacchi Loom • Virtual Threads are mounted on platform threads

    (carrier threads) • Yield in std library
  58. Game of Loom: implementation patterns and performance implications playing with

    virtual threads by Mario Fusco @mariofusco Learn More This Thursday!