It's the chat demo

Check out the demo!!

Way back when I was focused on Lift and explaining why Lift was different, I created the Lift chat app. The chat app was short, sweet, and highlighted how Lift was different.

As I've been working through the Dragonmark stuff, I decided to use the same Chat app as a demo. Why? 'cause the same concepts are present in Dragonmark... the abstraction of the cross-address-space plumbing.

core.async across address spaces

The underlying "what the heck is it?" of Dragonmark is Clojure's core.async across address spaces. Clojure's immutable, simply serializable data structures lend themselves very nicely to distribution. CSP does not assume sharing address spaces between processes. CSP simply assumes message sending.

Browser to server and back again

Clojure and ClojureScript are very cool because the developer has fundamentally the same language on the server and in the browser. Clojure and ClojureScript share the same data types and the same functions for operating on that data.

Further, the ClojureScript core.async implementation avoids the call-back challenges of the current state of the art in JavaScript land.

So, being able to distribute CSP channels across the browser and server and then send messages across the address spaces is a great concept... and with Dragonmark it just works.

Works great with Om

core.async works great with the Om browser app framework. Add Dragonmark to the mix and you get the same message handling in the browser, except the messages come from a different address space.

Show me some code

First, we'll take a look at the chat server:

(defn ^:service add-listener
  "Adds a listener. Pass in a channel to get messages"
  (swap! listeners conj the-chan)
  (async/put! the-chan  (vec (take-last 30 @messages)))

(defn ^:service remove-listener
  "Remove the listener"
  (swap! listeners (fn [vec] (filterv #(not (= the-chan %)) vec)))

(defn ^:service send-message
  "Sends a message into the chat stream"
  (if (string? msg)
      (swap! messages conj msg)
      (swap! listeners
             #(vec (remove asyncp/closed? %)))
      (mapv (fn [c] (async/put! c msg)) @listeners)

If you speak Clojure, the code is self-explanatory... except for the ^:service annotation. The ^:service thing tells Dragonmark that the function is part of a service that can be invoked via the gofor macro.

When the web server starts, a root message handler is created and the chat service is added to the root message handler:

 [_ (add base {:channel chat-service
               :public true
               :service "chat"})]
 (println "Added chat")
 :error (println "Add error " &err))

Each time a new session and a new page gets created, a handler is also created. The page handler delegates to the session handler and the session handler delegates to the root handler. Delegation is forwarding messages that the given handler does not understand.

The browser looks up the service:

 :let [other-root (circ/remote-root transport)]
 [the-chat-server (locate-service other-root {:service "chat"})]
 [_ (add-listener the-chat-server {:the-chan chat-listener})]
 (reset! chat-server the-chat-server)
 :error (.log js/console "Got error " &err " var " &var)

The remote-root is the handler in the other address space. The chat service is located with (locate-service other-root {:service "chat"}) and is placed in an atom.

The code also adds the chat-listener channel to the chat server's listeners.

The app handles messages from the chat service:

  (loop []
    (let [info (async/<! chat-listener)]
      (if (nil? info)
          (if (sequential? info)
            (swap! app-state assoc :chat info)
            (swap! app-state #(update-in % [:chat]
                                         (fn [x]
                                           (->> (conj x info)
                                                (take-last 30)

And a chat message is sent to the server with:

(defn- send-chat
  [data owner]
  (let [the-node (om/get-node owner "chat-in")
        chat-string (.-value the-node)
        chat-server @chat-server]
    (when (and
           (not (asyncp/closed? chat-server))
      ;; send the message to the chat server
      (async/put! chat-server
                  {:msg chat-string
                   :_cmd "send-message"})

      ;; clear the input
      (aset the-node "value" "")

So, there we have it.

We have code that may or may not be in a different address space. In fact, the chat server is implemented as both Clojure and ClojureScript... so it's totally possible to do all the prototyping of the app in the browser and then just switch to distributed mode.

A side note on "doing things right"

Timothy Baldridge took issue with my assertion in the gofor description that waiting for a reply from a process is the "right way" to do core.async.

Please let me clarify.

core.async channels, like the actor model has a default type signature of Any => Unit. What does that mean? It means you call the message send function (method) with any input and you get nothing back. Put another way, the message send function has no purpose other than to cause a side effect. But for the most part, in functional programming land, we try to avoid side effects.

And in fact, sometimes we send a message to a channel or a series of messages to a series of channels and care about getting the result of computations from some or all of the channels.

Put another way, sometimes we want Any => T where T is a type that's not unit/void.

The gofor macro allows a much more concise way of describing Any => T with automatic timeouts. Timeouts are a good thing because the remote channel may no longer be processing messages.

When I said "right way" I meant it in the same way that the right way to call open in C involves checking the return value for an error rather than just assuming it works.

There are plenty of core.async applications where Any => Unit is desired (please see David Nolen's excellent webinar on Om and core.async), then just put! those messages.

But if you care about doing Any => T right, Dragonmark and the gofor macro reduces the boilerplate in your code.

Where to?

Right now, the Dragonmark handler for browser/server communication is wired into the example code. The next step is to break the code out and make it a separate thing that can be used in your code.