Skip to content

Instantly share code, notes, and snippets.

@martintrojer
Created May 17, 2012 05:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save martintrojer/2716711 to your computer and use it in GitHub Desktop.
Save martintrojer/2716711 to your computer and use it in GitHub Desktop.
clojure-akka-vision
;; 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]))
(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])))
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);
}
}
}
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!"
}
}
@viktorklang
Copy link

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

@viktorklang
Copy link

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

@viktorklang
Copy link

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.

@martintrojer
Copy link
Author

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.

@viktorklang
Copy link

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?

@martintrojer
Copy link
Author

Can you sketch that up in Java? :)

@viktorklang
Copy link

Sorry, wonT have time, but you can check what we've done with TypedActors: https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/actor/TypedActor.scala#L217

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment