Looking for advise on function naming and signature

I have a few functions which allow me to add a channel to an a vector, lookup a channel by id or tag and send an event to a channel. The id is unique, the tag is not. What a channel is and what an event is is not important to my question, but for context a channel is a websocket channel and an event is a map which will get converted to JSON.

(def channels (atom []))

(defn register-channel! [id tag channel]
  (swap! channels conj [id tag channel]))

(defn find-by-id [id]
  (first (filter #(= id (first %)) @channels)))

(defn find-by-tag [tag]
  (filter #(= tag (second %)) @channels))

(defn send! [channel event] ...)

This works fine, I could have stored maps in the atom instead of arrays, it might have made it a little more readable, but for now it works.

My question is around the function names and signatures for the functions to send an event to a single channel by id or multiple channels by tag.

I have a few options, initially I thought about two functions:

(defn send-event-by-id! [id event] ...)
(defn send-event-by-tag! [tag event] ...)

Simple enough. But I wasn’t too keen on the function names, the by-id / by-tag suffixes didn’t feel right. I can’t articulate why.

So I thought maybe I can just have a single function which can be passed an id or tag, an id is a String and a tag is a Keyword. I would add a pre condition to register-channel! to enforce this.

(defn send-event! [id-or-tag event]
  (case (class id-or-tag)
    java.lang.String (...)
    clojure.lang.Keyword (...)))

This worked and I also tried a version using a multimethod:

(defmulti send-event! (fn [id-or-tag event] (class id-or-tag)))

But in the end, since I will not be adding any new identifiers I thought a multimethod was overkill and may have a performance hit.

Other options which occurred to me:

(defn send-event! [query event] ...)

(send-event! { :by-id 1 } { ... })
(send-event! { :by-tag :player } { ... })

and

(defn send-event! [key value event] ...)

(send-event! :id "1234" {...})
(send-event! :tag :player {...})

and

(defn send-event! [id event] ...)
(defn send-events! [tag event] ...)

So… are any more idiomatic for Clojure. Any comments? Am I over thinking it and should have just gone with my first thought, two functions.

The proposed function is designed as a one-parameter function with a hash-map type. Like most functions of the R language, many named parameters with default values can be designed and have strong extensibility. In addition, the core functions of the clojure operation hash-map are many, easy to operate, not only when using ->> macros don’t need write parentheses, and can integrate the formation of parameters, verification, transformation and function calls, one-stop data stream processing. Clojure deconstruction is convenient, and the use of formal parameters in the function body is as convenient as general multi-parameter functions.
Multiparameter functions are not necessary, Add control flags in the hash-map parameter
deconstruction usage

(defn f [{:keys [x y] :as m}]
  (-> (> x 2)
      (and , (< y 6))
      (if , 25 30)))
1 Like

I’d keep the two functions separate as they are related, but ultimately have different responsibilities. IMO two functions would be more clear to future readers of the code and you’d get help from most editors with code completion and refactoring. As far as names, I understand your desire to not use by-id and by-tag, perhaps these names would work:

(defn unicast-event [receiver-id event] ...)
(defn broadcast-event [broadcast-tag event] ...)

If you don’t like unicast-event, perhaps using send-event for that case?

http://www.erg.abdn.ac.uk/users/gorry/course/intro-pages/uni-b-mcast.html

Cheers,
Shaun

It will take me a moment to unpack what you have said, it is quite dense :slight_smile: Could you explain what you mean by “control flags” please.

Thanks for the suggestions. I like your naming, similar to send-event! / send-events!, but different enough not to misread one as the other.

The control flag is equivalent to the metadata written in the data and controls the behavior of subsequent code.

It is explicit, easy to dynamic watch when debugging.

Data self explanatory, also support multithreading to operate in random order

By the way, Java classes are not constants (you can have the same class loaded multiple times by different classloaders) and you should not use them as case values. One workaround is to use (str (class id-or-tag)) and "java.lang.String" etc.

2 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.