art-solopov
art-solopov

Reputation: 4755

Is gathering namespace functions into a map via a macros idiomatic Clojure?

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?

Thank you.

Upvotes: 1

Views: 87

Answers (2)

amalloy
amalloy

Reputation: 91907

I think you're in a weird spot because you've structured your program wrong:

Each worker is defined in their own namespace as a set of functions

This is the real problem. Namespaces are a good place to put functions and values that you will refer to in hand-written code. For stuff you want to access programmatically, they are not a good storage space. Instead, make the data you want to access first-class by putting it into an ordinary proper data structure, and then it's easy to manipulate.

For example, this worker-info map you're thinking of deriving from the namespace is great! In fact, that should be the only way workers are represented: as a map with keys for the worker's functions. Then you just define somewhere a list (or vector, or map) of such worker maps, and that's your list of workers. No messing about with namespaces needed.

Upvotes: 6

Toni Vanhala
Toni Vanhala

Reputation: 1372

My go-to solution for defining the workers would be Protocols. I would also apply some of the well-tried frameworks for system lifecycle management.

Protocols provide a way of defining a set of methods and their signatures. You may think of them as similar, but more flexible than, interfaces in object-oriented programming.

Your workers will probably have some state and life-cycle, e.g., the workers may be running or stopped, acquiring and releasing a resource, and so on. I suggest you take a look at Integrant for managing a system with stateful components (i.e., workers).

I would argue for avoiding macros in this case. The adage data over functions over macros seems to apply here. Macros are not available at runtime, make debugging harder, and force all other programmers who look at your code to learn a new Domain-Specific Language, i.e., the one you defined with your macros.

Upvotes: 3

Related Questions