This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-04-03
Channels
- # announcements (5)
- # babashka (8)
- # beginners (98)
- # biff (2)
- # calva (20)
- # cider (16)
- # clerk (2)
- # clj-kondo (20)
- # cljdoc (19)
- # clojure (90)
- # clojure-art (3)
- # clojure-boston (1)
- # clojure-europe (7)
- # clojure-nl (2)
- # clojure-norway (47)
- # clojure-uk (3)
- # clojurescript (10)
- # cursive (10)
- # data-science (1)
- # datalevin (1)
- # defnpodcast (1)
- # events (2)
- # fulcro (11)
- # gratitude (2)
- # honeysql (18)
- # hyperfiddle (11)
- # introduce-yourself (1)
- # jobs (2)
- # lambdaisland (4)
- # lsp (6)
- # malli (4)
- # membrane (3)
- # off-topic (58)
- # polylith (14)
- # portal (2)
- # releases (2)
- # ring-swagger (4)
- # tools-deps (8)
- # xtdb (8)
I'm building a ClojureScript wrapper for a JS library. Basically all the methods exposed are in one namespace. I would like to be able to offer alternative, no-op implementation for all these methods so the user can turn it off for example during testing. What would be an idiomatic way to do it in Clojure, that doesn't require re-compilation? In other words the user could switch to a different implementation just by starting the app with different configuration parameter. An idea that came to my mind was to implement the same functions in another namespace but this can lead to the signatures (arg lists) getting out of sync and functions too (being present in one but not in another ns). Can you recommend better ways?
(ns api.doing-work)
(defn send-message [content]
(http/post "/messages" {:content content}))
(defn send-alert [content]
(messaging/alert content))
;; And in another file
(ns api.no-op)
(defn send-message [content])
(defn send-alert [content])
There are no magic solutions here. You can use a macro or a namespace swap - they both require recompilation to take effect. The latter can also lead to the signatures issue that you've pointed out. You can use a swappable function registry or the simplest thing - runtime conditions. These approaches can be implemented in a way that doesn't require recompilation.
Although the signatures issue can be alleviated by simply making all functions in no-op
having [& _]
signature.
But of course, that wouldn't fix the issue of missing or extra functions. And it still require recompilation to swap namespaces.
there's a few methods (some are more annoying cljs). My favorite is something like:
(ns api.imessage)
(defprotocol IMessage
(send-message [_ content])
(send-alert [_ content]))
(ns api.doing-work)
(defn send-message [content]
(http/post "/messages" {:content content}))
(defn send-alert [content]
(messaging/alert content))
(def messenger
(reify
IMessage
(send-message [_ content]
(send-message content))
(send-alert [_ content]
(send-alert content))))
;; And in another file
(ns api.no-op)
(defn send-message [content])
(defn send-alert [content])
(def messenger
(reify
IMessage
(send-message [_ content])
(send-alert [_ content])))
(ns other.ns)
(def messenger (if foo work/messenger no-op/messenger)
(api.imessage/send-message messenger content)
• Users who only care about using a single implementation can use the namespace directly if they want.
• Implementing it via a protocol also provides a good place (eg. defrecord) to put config if that comes up in the future I have a lot less experience on the cljs side of things, but in general I think clients written like this, that just directly do http posts etc, without reifying the client as a thing, are bad
A client should have something like a make-client function, and all functions that do something should take whatever make-client returns as an argument
And it allows for things like multiple instances of the client configured differently to coexist
This is why I love the clojure community, always getting good ideas and solutions! @U7RJTCH6J I'll try that one! Haven't had to use protocols before (only read about them). And @U0NCTKEV8 thanks to you too! The examples I put there were imaginary to keep things short... I wouldn't do that in real app 🙂