A pipeline of agents creates a fully asynchronous programming model

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com

I recall reading this code in 2010 or 2011, when I was first learning Clojure, and at the time there was a great deal about it that I did not understand, so I missed the point:

(def logger (agent (list)))
(defn log [msg]
  (send logger #(cons %2 %1) msg))

(defn create-relay [n]
     (letfn [(next-agent [previous _] (agent previous))]
     (reduce next-agent nil (range 0 n))))

(defn relay [relay msg]
  (letfn [(relay-msg [next-actor hop msg]
               (cond (nil? next-actor)  (log "finished relay")
                 :else (do (log (list hop msg))
                          (send next-actor relay-msg (+ hop 1) msg))))]
    (send relay relay-msg 0 msg)))

(relay (create-relay 10) "hello")
(. java.lang.Thread sleep 5000)
(prn @logger)

I especially misread this:

(defn create-relay [n]
(letfn [(next-agent [previous _] (agent previous))]
(reduce next-agent nil (range 0 n))))

I think I thought that since “nil” was the initial “previous” then “previous” had to remain “nil” throughout, since it was never given a different value. But now I am re-reading it. If I do this at that REPL:

(defn create-relay [n]
     (letfn [(next-agent [previous _]
            (clojure.pprint/pprint previous)
                  (agent previous))]
     (reduce next-agent nil (range 0 n))))

I see:

nil
#&lt&Agent@46b8b04: nil&gt&
#&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&
#&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&
#&lt&Agent@791de131:
  #&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&
#&lt&Agent@6091eb5b:
  #&lt&Agent@791de131:
    #&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&
#&lt&Agent@6b3469e7:
  #&lt&Agent@6091eb5b:
    #&lt&Agent@791de131:
      #&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&&gt&
#&lt&Agent@6e4d8b17:
  #&lt&Agent@6b3469e7:
    #&lt&Agent@6091eb5b:
      #&lt&Agent@791de131:
        #&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&&gt&&gt&
#&lt&Agent@6501c73f:
  #&lt&Agent@6e4d8b17:
    #&lt&Agent@6b3469e7:
      #&lt&Agent@6091eb5b:
        #&lt&Agent@791de131:
          #&lt&Agent@ef9cd40:
            #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&
#&lt&Agent@4275159c:
  #&lt&Agent@6501c73f:
    #&lt&Agent@6e4d8b17:
      #&lt&Agent@6b3469e7:
        #&lt&Agent@6091eb5b:
          #&lt&Agent@791de131:
            #&lt&Agent@ef9cd40:
              #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&
#&lt&Agent@2f5cfab2: #&lt&Agent@4275159c: #&lt&Agent@6501c73f: #&lt&Agent@6e4d8b17: #&lt&Agent@6b3469e7: #&lt&Agent@6091eb5b: #&lt&Agent@791de131: #&lt&Agent@ef9cd40: #&lt&Agent@13f8705c: #&lt&Agent@46b8b04: nil&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&&gt&

I see that the first value is “nil”, but then we have an agent that holds nil, then we have an agent that holds an agent that has nil, etc, until we have 10 agents, each holding another, except for the first one that holds nil.

This text now, finally, makes sense to me:

Probably the interesting aspect of this example is using agents to send agents to other agents which creates a fully asynchronous programming model.

Further, although this is a rather convoluted example in itself, the message relay is actually the simplest version of a work pipeline where each agent performs some manipulation on the incoming message before passing it further down the line.

Source