clojureclojure.spec

Can I validate functions with Clojure spec?


Can I use the Clojure spec system to define function signatures and verify if functions satisfy them?

Here are some examples I've tried without success

(s/valid? (s/fspec :args string? :ret string?) identity) ;; false

(def nope identity)
(s/valid? (s/fspec :args string? :ret string?) nope) ;; false

(s/conform (s/fspec :args string? :ret string?) identity) ;; invalid

(defn strmap [s] {:pre [(s/valid? string? s)] :post [(s/valid? string? %)]} s)
(s/valid? (s/fspec :args string? :ret string?) strmap) ;; false

(s/fdef strmap :args string? :ret string?)
(s/valid? strmap strmap) ;; true

(s/def ::str-pred (s/fspec :args string? :ret boolean?))
(s/valid ::str-pred (fn [s] true)) ;; false

I know about fdef, but I'd like something I can compose. For example, creating a map of related function signatures.


Solution

  • Turns out spec does handle functions, but it expects the arguments to be a tuple.

    (s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) identity) ;; true!
    (s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] (str x "hi there"))) ;; true
    (s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] x)) ;; true
    
    (s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] 42)) ;; false
    

    There does appear to be some issues with the anonymous function literal. I'm probably misunderstanding something about the macro expansion.

    (s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) #(%)) ;; false