Fork me on GitHub
#clojure-spec
<
2022-10-30
>
jmmk03:10:33

is it a bad idea to import/rename a spec. Or is there a better way to accomplish this I have a few different something/client.clj namespaces and I need to pass instances of the clients to a handler constructor. since the unqualified name client conflicts, I create new names for them e.g.

(ns myapp.api.client 
  (:require [clojure.spec.alpha :as s]))

(s/def ::client (partial satisfies? ApiClient))
(ns myapp.other.client 
  (:require [clojure.spec.alpha :as s]))

(s/def ::client (partial satisfies? OtherClient))
(ns myapp.handler 
  (:require [clojure.spec.alpha :as s] 
            [myapp.api.client :as ac] 
            [myapp.other.client :as oc]))

(s/def ::api-client ::ac/client)
(s/def ::other-client ::oc/client)

;; handler constructor accepts config with :api-client and :other-client
(s/def ::config (s/keys :req-un [::api-client ::other-client]))

seancorfield03:10:33

On the face of it, this seems reasonable to me... although that last line is missing :: yes?

seancorfield03:10:23

(and now your ::config structure has to have unqualified keys called :api-client and :other-client -- is that what you intended?)

jmmk04:10:24

> although that last line is missing :: yes? Yes, those keys should have :: > (and now your ::config structure has to have unqualified keys called :api-client and :other-client -- is that what you intended?) It is what I intended, but I am not sure if my reasoning is sound. For a bit more context, I’m using integrant, so these client args will be passed from a “system map” I guess it boils down to wanting to use shorter/easier keys (while still disambiguating the two separate clients) to refer to these, both within the system config and within the handler.

cjohansen11:10:46

You are effectively throwing away the namespace that originally disambiguated them and then giving them new names. Why not just use namespaced keys instead? Less indirection, easier to understand what things are.

jmmk13:10:42

It feels a bit weird to me that map keys are tied to their values/semantics, but maybe that's the "spec way" and I just need some more time getting used to it? it also feels verbose in the config and verbose to destructure because they both share the name client and at some point I have to change the name anyway

;;;; rename

;; config
:myapp/handler
{:other-client #ig/ref :myapp.api/client
 :api-client #ig/ref :myapp.other/client}

;; construct
(s/def ::api-client :ac/client)
(s/def ::other-client :oc/client)

(defmethod ig/init-key :myapp/handler
  [_ {:keys [api-client other-client]}]
  (handler api-client other-client))
vs
;;;; use namespaced keywords throughout

;; config
:myapp/handler
{:myapp.api/client #ig/ref :myapp.api.client
 :myapp.other/client #ig/ref :myapp.other/client}

;; construct
(defmethod ig/init-key :myapp/handler
  [_ config]
  (let [{api-client ::ac/client
         other-client ::oc/client} config]
    (handler api-client other-client)))

seancorfield16:10:36

I wouldn't bother destructuring them for that call -- I'd use (handler (::ac/client config) (::oc/client config))

👍 1
jmmk17:10:42

Would it be fair to say then that if I am using a spec and want to pass instances (or things that conform to that spec) in a map that it makes the most sense to use the namespaced keyword for that, regardless of the extra verbosity?

jmmk18:10:46

And a followup to that: If preferring the full keyword is generally a good idea, what then is an appropriate use case for req-un ? Is it for things you don’t want/need to specify as precisely? beyond the above potential misuse of it, I am using it for things like port, host config

seancorfield19:10:00

@U08FV2128 Namespaced keywords are idiomatic and I think in the case you're describing -- passing two different configurations into another function -- it makes sense for the original hash map to use namespaced keywords. If you have keys that "belong to" or are "associated with" some specific concept and/or subsystem within your app, then namespaced keywords mean you can talk about those things everywhere within your app without any conflicts, and also see where the data comes from or is going to.

seancorfield19:10:33

Unqualified keywords are fine when there is no possibility of a conflict and/or there is no inherent "domain" associated with the key. I'd say, for a web app that starts up a server on a given port, listening to a specific host, then it's fine to use :port and :host there. If you mean port/host for specific services you access from within your app, it's probably better to qualify them unless there's a strong reason not to. For example, interacting with a database via next.jdbc is going to require unqualified keywords for :port, :host, etc so it might make sense to have :db/config as a key in your overall config whose value is a hash map with :port, :host, etc. Keywords don't need to match namespaces. At work we use qualifiers like wsbilling and wsmessaging to identify data belonging to those subsystems as high-level keys (but we also have a mix of full namespace qualified keys and unqualified keys across the apps for various contexts).

jmmk19:10:01

> Keywords don't need to match namespaces. At work we use qualifiers like wsbilling and wsmessaging to identify data belonging to those subsystems as high-level keys (but we also have a mix of full namespace qualified keys and unqualified keys across the apps for various contexts). How do you use those non-namespaced keywords with specs?

seancorfield19:10:56

(s/def :wsbilling/id int?) and then (s/def :wsbilling/transaction (s/keys :req [:wsbilling/id ..])) -- is that what you're asking?

cjohansen20:10:57

To use non-namespaced keys with spec you use :req-un and :opt-un

jmmk22:10:39

> Keywords don’t need to match namespaces It didn’t click until I saw your example that specs can use arbitrary namespaces @U04V70XH6 @U9MKYDN4Q thanks for all the advice. I am getting back into a clojure project I wrote many years ago as a pile of spaghetti and trying to impose some order on it and use spec instead of my home-grown data validation/coercion