1
\$\begingroup\$

I'm learning Clojure via a pet project. The project would consist of several workers that would be called from other functions.

Each worker is defined in their own namespace as a set of functions (currently two: get-data for gathering data and write-data for writing the gathered data into a file).

In order to make the code a bit DRYer, I decided to write a macro that would gather functions from namespace into a map that can be passed around:

(ns clojure-bgproc.workers)

(defmacro gen-worker-info []
  (let [get-data (ns-resolve *ns* 'get-data)
        write-data (ns-resolve *ns* 'write-data)]
    `(def ~(quote worker-info)
       {:get-data ~get-data
        :write-data ~write-data}
       )
    )
  )

In my worker code, I use my macro (code abridged for clarity):

(ns clojure-bgproc.workers.summary
  (:require [clojure-bgproc.workers :refer [gen-worker-info]]))

(defn get-data [params]
  <...>
  )

(defn write-data [data file]
  ;; <...>
  )

(gen-worker-info)

While it does work (I get my get-data and write-data functions in clojure-bgproc.workers.summary/worker-info, I find it a bit icky, especially since, if I move the macro call to the top of the file, it doesn't work.

My question is, is there a more idiomatic way to do so? Is this idiomatic Clojure at all?

P. S. It looks like I haven't made my intentions quite clear, so I've decided to include a usage example:

(ns clojure-play.play-worker
  (:require [clojure.string :refer (join)])
  )

;; Those two workers will be pulled from other namespaces
(def worker1
  {:get-data (fn [] (take 10 (repeatedly #(int (rand 10)))))
   :write-data (fn [data] (join "," data))}
  )

(def worker2
  {:get-data (fn []
               (take 10 (repeatedly #(int (rand 50))))
               )
   :write-data (fn [data]
                 (join "\n" (map #(str "<row>" % "</row>") data))
                 )
   }
  )

(def workers
  {"worker1" worker1 "worker2" worker2}
  )

(defn -do-work [worker-name]
  (let [worker (workers worker-name)
        get-data (worker :get-data)
        write-data (worker :write-data)]
    (write-data (get-data))
    )
  )

(defn example []
  (println "Worker 1 results:")
  (println (-do-work "worker1"))
  (println)
  (println "Worker 2 results:")
  (println (-do-work "worker2"))
  )

When I run (example), this is what I get:

Worker 1 results:
5,9,6,2,4,6,0,0,7,0

Worker 2 results:
<row>9</row>
<row>26</row>
<row>42</row>
<row>37</row>
<row>1</row>
<row>38</row>
<row>0</row>
<row>31</row>
<row>10</row>
<row>35</row>
\$\endgroup\$
  • \$\begingroup\$ Do you ever need to call the functions directly (by name instead of using the map)? Will the two functions always accept the same parameters? I would say no, you shouldn't be doing this, but without more info, I can't suggest an alternative. Defining a protocol or multimethods seem like they may be appropriate. \$\endgroup\$ – Carcigenicate Aug 18 '18 at 14:56
  • \$\begingroup\$ @Carcigenicate Yes to both... I'll try to write a usage example when I get to my PC. \$\endgroup\$ – art-solopov Aug 18 '18 at 15:41
  • 1
    \$\begingroup\$ Without seeing that usage example, I can't say for sure (I'm going out for the day in a bit, so I can't wait for it to be posted). You could try a setup like this though: gist.github.com/carcigenicate/cf927b132472b0a01686b150dea24db4 \$\endgroup\$ – Carcigenicate Aug 18 '18 at 16:21
  • 1
    \$\begingroup\$ I'll note though that my above example works if you have a fairly closed set of workers. It won't be feasible for an "end user" to add new workers using this setup. The suggestion to use protocols (or multimethods) would allow for future additions, but I couldn't think of a clean way to add them since your code just boils down to pairs of functions. \$\endgroup\$ – Carcigenicate Aug 18 '18 at 17:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.