This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-22
Channels
- # announcements (39)
- # architecture (9)
- # aws (2)
- # babashka (17)
- # beginners (73)
- # calva (6)
- # cider (27)
- # clj-kondo (140)
- # cljdoc (67)
- # cljsrn (1)
- # clojure (99)
- # clojure-dev (4)
- # clojure-europe (35)
- # clojure-nl (7)
- # clojure-spec (19)
- # clojure-uk (2)
- # clojurescript (40)
- # community-development (3)
- # cursive (10)
- # datalevin (2)
- # datavis (2)
- # datomic (27)
- # deps-new (5)
- # events (2)
- # fulcro (38)
- # integrant (6)
- # jobs (3)
- # keyboards (1)
- # leiningen (13)
- # lsp (3)
- # malli (10)
- # meander (5)
- # membrane (1)
- # membrane-term (9)
- # missionary (3)
- # off-topic (29)
- # polylith (3)
- # reagent (3)
- # reitit (5)
- # remote-jobs (2)
- # reveal (7)
- # shadow-cljs (20)
- # tools-build (4)
- # tools-deps (8)
- # vim (10)
- # xtdb (3)
Hi, everyone. I have a beginner question about Spec's namespacing as it pertains to maps. I'd like to create two definitions within the namespace, such that the following passes:
(s/conform ::account {:account-id "abc123", :amount 50})
(s/conform ::transaction {:from-account "abc123", :to-account "def456", :amount 25})
Note that both definitions reference the same non-namespaced key of amount
, but I'd like to define the amount
of an account
to be an int between -100 and 100, and the amount
of a transaction
to be any positive int. Furthermore, I'd like to use the same spec for the account-id
on an account
, as well as both the from-account
and to-account
keys in a transaction
.
I can't see any way within a map to say that the value of a particular key corresponds to a specification by name. I'm trying to find a way to do something like this, but can't see how:
; This is not working code... I'm trying to find a way to get here.
(s/def ::transaction-amount pos-int?)
(s/def ::balance-amount #(<= -100 % 100))
(s/def ::account-id string?)
; Both :from-account and :to-account should be mapped to the spec for ::account-id
(s/def ::transaction (s/keys :req-un [[:from-account :as ::account-id]
[:to-account :as ::account-id]
[:amount :as ::transaction-amount]]))
; The :amount here is a ::balance-amount, but the :amount for a transaction is a ::transaction-amount
(s/def ::account (s/keys :req-un [::account-id
[:amount :as ::balance-amount]]))
(s/def :account/amount ...)
(s/def :transaction/amount ...)
Then you can use those qualified names in the :req-un
part and it will match :account
in both cases, with different semantics.Although a lot of examples use ::
that's just shorthand for a qualified name that "happens to match" the current namespace -- but qualified keywords should really really reflect their domain semantics -- they don't need to match code namespaces.
Does that help @abyala?
With the account IDs, you can define a common ::account-id
and then just define the other names as aliases:
(s/def ::account-id ..)
(s/def ::from-account ::account-id)
(s/def ::to-account ::account-id)
And, again, as above you can use qualified names that match the domain semantics rather than ::
for the code namespace.Nifty, @seancorfield. This seems to work for everything I'm looking for:
; This is reused for both transactions and accounts.
(s/def ::account-id string?)
(s/def :transaction/from-account ::account-id)
(s/def :transaction/to-account ::account-id)
(s/def :transaction/amount pos-int?)
(s/def ::transaction (s/keys :req-un [:transaction/from-account :transaction/to-account :transaction/amount]))
(s/def :account/account-id ::account-id)
(s/def :account/amount #(<= -100 % 100))
(s/def ::account (s/keys :req-un [:account/account-id :account/amount]))
Is that what you were suggesting?
That looks great, yes!
And now you can see how the qualified keys provide separate semantic "namespaces" rather than code namespaces.
Yep, I see that. The piece I was missing was how :req-un
would strip that semantic namespace back off again, letting the actual key be whatever I wanted, Super helpful!
Separate question -- do you find you tend to create a separate namespace just to hold specs for other namespaces, especially if they're shared? Or do you tend to put specs within the namespaces containing other logic?
Specs for data structures tend to live in their own namespace, no code. Specs for functions live in the same namespace as the function. Roughly.
I wrote about our use of Spec here https://corfield.org/blog/2019/09/13/using-spec/ based on what we do at World Singles Networks.

Has it been hammocked (officially or not) to give the notion of coverage to specs?
e.g. for an (s/or)
, verify at runtime, integrated with a given test suite, that all branches of the or
have been exercised.
(I know that generative testing is a thing! I want it the other way around here: start by code and reach the spec, not vice versa)
One big use case, is that for any defn that returns s/or :result ,,, :error ,,,
I should rest assured that the test suite has coverage for the green and red paths of the spec alike
Haven't talked about it
Is there a writeup anywhere about the reason that instrument
doesn't validate :ret
and :fn
? I was wondering if this was considered and decided against, or there were some non-obvious difficulties. There's a library out there that does this, but I've got this nagging suspicion that it was left out of the first-party spec library for a reason.
Ah my bad, should have looked around for a FAQ, of course it's on there. Thank you!