public
Created

clojure-akka-vision

  • Download Gist
Hello.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
package akka.tutorial.first.java;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;
import akka.actor.ActorSystem;
import akka.actor.Props;
public class Hello {
public static void main(String[] args)
{
final ActorSystem system = ActorSystem.create("hellokernel");
Props props = new Props().withCreator(new UntypedActorFactory() {
public UntypedActor create() {
return new HelloActor();
}
});
ActorRef helloActor = system.actorOf(props);
helloActor.tell("start");
}
public static class HelloActor extends UntypedActor {
final ActorRef worldActor =
getContext().actorOf(new Props(new UntypedActorFactory() {
public UntypedActor create() {
return new WorldActor();
}
}));
 
public void onReceive(Object message) {
if (message instanceof String) {
String strmess = (String) message;
if (strmess == "start")
worldActor.tell("Hello", getSelf());
else
System.out.println("Received message '%s'".format((String)message));
}
else unhandled(message);
}
}
 
public static class WorldActor extends UntypedActor {
public void onReceive(Object message) {
if (message instanceof String)
getSender().tell(((String)message).toUpperCase() + " world!");
else unhandled(message);
}
}
}
Hello.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
package sample.hello
 
import akka.actor.{ ActorSystem, Actor, Props }
 
case object Start
 
object Main {
def main(args: Array[String]): Unit = {
val system = ActorSystem()
system.actorOf(Props[HelloActor]) ! Start
}
}
 
class HelloActor extends Actor {
val worldActor = context.actorOf(Props[WorldActor])
def receive = {
case Start worldActor ! "Hello"
case s: String
println("Received message: %s".format(s))
context.system.shutdown()
}
}
 
class WorldActor extends Actor {
def receive = {
case s: String sender ! s.toUpperCase + " world!"
}
}
clojure-builtin-actor-vision.clj
Clojure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
;; An (imaginary) actor macro takes an initial state and callback fns.
;; (actor {} (fn1 [state message]) (fn2 [state message) ...)
 
;; The most obvious callback fn is (receive [state message) that is called when a
;; message is consumed from the agents mailbox. receive is called with the old state
;; and the message as parameters -- it returns the new state.
;; sender is bound to the senders pid.
 
;; other callbacks are stuff broken link detection etc.
 
;; (spawn) creates a lightweight actor process, also has remote-actor semantics
;; (tell) is used to send messages to an spawned actor's pid.
 
(def hello-actor
(actor {:world-actor
(spawn
(actor {}
(receive [state [message-type word :as message]]
(condp = message-type
:hello (do
(tell sender (str (.toUpperCase word) "world!"))
state)
(unhandled message)))))}
(receive [state [message-type word]]
(condp = message-type
:start (do
(tell (:world-actor state) [:hello "hello"])
state)
(do
(println (str "Received message:" word))
(shutdown)))))
 
(let [pid (spawn hello-actor)]
(tell pid [:start]))
commented-vision.clj
Clojure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
(ns cljakka.vision
(:import [akka.actor CljActor ActorSystem Props]))
 
;; I want to be able to create a class based on an "Actor" interface.
;; It's important that both the class and object can be defined and created
;; on the fly in the repl.
 
(defrecord hello-actor [world-actor]
CljActor
 
;; In idiomatic Clojure, I don't want to create a Class for each message,
;; instead I want (like in Erlang) all messages to be a basic collection
;; (like vector) and switch (match) on a keyword (atom) on one of the positions.
(onReceice [this [message-type word :as message]]
(condp = message-type
:start (.tell world-actor [:hello "hello"])
(do ;; else case
(println (str "Received message:" word))
(-> this .getContext .getSystem .shutdown)))))
 
(defrecord world-actor []
CljActor
(onReceive [this [message-type word :as message]]
(cond = message-type
:hello (.tell (.getSender this)
[(str (.toUpperCase word) "world!")])
;; else case
(.unhandled this message))))
 
;; The defrecords above creates a new class that implements the new interface,
;; it is then instanciated like (world-actor.)
 
;; There are other (niftier) ways in Clojure to create an object directly
;; on an interface directly without needing to define the class first
;; (with proxy and reify) - this create anonymous classes and is powerful.
;; However, this totally screws up Akka's factories, but I wants it! :)
 
(def world-actor2
(reify CljActor
(onRecieve [this message]
(.unhandled this message))))
 
;; Now, it is possible to generate a "proper" class like in the java examples.
;; You can do this in clojure with (gen-class), but that requires a compile
;; step. Which is for a clojure developer appaling. We live and
;; breathe in the REPL. See how awful it becomes;
;; https://github.com/martintrojer/clojak/blob/master/src/clojak/core.clj
 
(defn -main [& args]
(let [system (ActorSystem.)
;; here system.actorOf takes an object!
world-act (.actorOf system (world-actor.))
;; or perhaps the Props ctor takes an object?
world-act2 (.actorOf system (Props. (world-actor.)))
 
hello-act (.actorOf system (Props. (hello-actor. world-act)))
;; or Props takes the class, but how do create it with arguments?
hello-act2 (.actorOf system (Props. hello-actor))]
(.tell hello-act [:start])))

You do not have to create a class for every message.

This sentence carries no meaning " However, this totally screws up Akka's factories, but I wants it! :)" can you elaborate?

So in general, if you want to be able to use lifecycle things like preRestart etc you'll need to provide a class that extends UntypedActor.
Can't you just make some utility that givens something from Clojure generates an UntypedActorFactory that returns an instance of a class that extends UntypedActor?
Then your Clojure code will look pretty and things will still work properly.

Viktor - re comment 2. Passing (anonymus class) objects that extends an interface (with Clojure's proxy and reify) doesn't work with Factories that requires class literals (obviously). I do want it to work :)

Comment 3; Not sure this is possible in Clojure, we can't pass Class literals around like in Java.

Create an interface bridge so you have one method that takes an instance of an interface and then returns the concrete class that delegates all ops to the interface?

Can you sketch that up in Java? :)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.