Wie kann ich meine Spezifikationen für den vorgesehenen Zweck verwenden, wenn sie sich in einem separaten Namespace befinden?

Eines der Beispiele imclojure.spec Leite ist eine einfache Option-Parsing-Spezifikation:

(require '[clojure.spec :as s])

(s/def ::config
  (s/* (s/cat :prop string?
              :val (s/alt :s string? :b boolean?))))

(s/conform ::config ["-server" "foo" "-verbose" true "-user" "joe"])
;;=> [{:prop "-server", :val [:s "foo"]}
;;    {:prop "-verbose", :val [:b true]}
;;    {:prop "-user", :val [:s "joe"]}]

päter im validation Abschnitt ist eine Funktion definiert, die internconforms seine Eingabe mit dieser Spezifikation:

(defn- set-config [prop val]
  (println "set" prop val))

(defn configure [input]
  (let [parsed (s/conform ::config input)]
    (if (= parsed ::s/invalid)
      (throw (ex-info "Invalid input" (s/explain-data ::config input)))
      (doseq [{prop :prop [_ val] :val} parsed]
        (set-config (subs prop 1) val)))))

(configure ["-server" "foo" "-verbose" true "-user" "joe"])
;; set server foo
;; set verbose true
;; set user joe
;;=> nil

Da der Leitfaden aus der REPL leicht zu entnehmen sein soll, wird der gesamte Code im selben Namespace ausgewertet. Imdiese Antwortlevand empfiehlt jedoch, Spezifikationen in separaten Namespaces zu platzieren:

Ich füge normalerweise Specs neben dem von ihnen beschriebenen Namespace in einen eigenen Namespace ein.

Dies würde die Verwendung von @ unterbrech::config oben, aber dieses Problem kann behoben werden:

Die Namen von Spezifikationsschlüsseln sollten vorzugsweise im Namespace des Codes liegen, nicht jedoch im Namespace der Spezifikation. Dies ist immer noch einfach, wenn ein Namespace-Alias für das Schlüsselwort verwendet wird:

(ns my.app.foo.specs
  (:require [my.app.foo :as f]))

(s/def ::f/name string?)

Er fährt fort zu erklären, dass Spezifikationen und Implementierungenkönnte in den gleichen Namespace gesetzt werden, aber es wäre nicht ideal:

Während ich sie in derselben Datei neben den angegebenen Code stellen könnte, schadet dies der Lesbarkeit, IMO.

Allerdings habe ich Probleme zu sehen, wie dies mit @ arbeiten ka destructuring. Als Beispiel habe ich ein kleines @ zusammengestelStiefe Projekt mit dem obigen Code in mehrere Namespaces übersetzt.

boot.properties:

BOOT_CLOJURE_VERSION=1.9.0-alpha7

src/example/core.clj:

(ns example.core
  (:require [clojure.spec :as s]))

(defn- set-config [prop val]
  (println "set" prop val))

(defn configure [input]
  (let [parsed (s/conform ::config input)]
    (if (= parsed ::s/invalid)
      (throw (ex-info "Invalid input" (s/explain-data ::config input)))
      (doseq [{prop :prop [_ val] :val} parsed]
        (set-config (subs prop 1) val)))))

src/example/spec.clj:

(ns example.spec
  (:require [clojure.spec :as s]
            [example.core :as core]))

(s/def ::core/config
  (s/* (s/cat :prop string?
              :val (s/alt :s string? :b boolean?))))

build.boot:

(set-env! :source-paths #{"src"})

(require '[example.core :as core])

(deftask run []
  (with-pass-thru _
    (core/configure ["-server" "foo" "-verbose" true "-user" "joe"])))

Aber natürlich bekomme ich beim Ausführen eine Fehlermeldung:

$ boot run
clojure.lang.ExceptionInfo: Unable to resolve spec: :example.core/config

Ich konnte dieses Problem beheben, indem ich @ hinzufüg(require 'example.spec) zubuild.boot, aber das ist hässlich und fehleranfällig und wird immer größer, je mehr Namespaces ich habe. Ich kann nichtrequire Der Spec-Namespace aus dem Implementierungs-Namespace. Hier ist ein Beispiel, das @ verwendfdef.

boot.properties:

BOOT_CLOJURE_VERSION=1.9.0-alpha7

src/example/spec.clj:

(ns example.spec
  (:require [clojure.spec :as s]))

(alias 'core 'example.core)

(s/fdef core/divisible?
  :args (s/cat :x integer? :y (s/and integer? (complement zero?)))
  :ret boolean?)

(s/fdef core/prime?
  :args (s/cat :x integer?)
  :ret boolean?)

(s/fdef core/factor
  :args (s/cat :x (s/and integer? pos?))
  :ret (s/map-of (s/and integer? core/prime?) (s/and integer? pos?))
  :fn #(== (-> % :args :x) (apply * (for [[a b] (:ret %)] (Math/pow a b)))))

src/example/core.clj:

(ns example.core
  (:require [example.spec]))

(defn divisible? [x y]
  (zero? (rem x y)))

(defn prime? [x]
  (and (< 1 x)
       (not-any? (partial divisible? x)
                 (range 2 (inc (Math/floor (Math/sqrt x)))))))

(defn factor [x]
  (loop [x x y 2 factors {}]
    (let [add #(update factors % (fnil inc 0))]
      (cond
        (< x 2) factors
        (< x (* y y)) (add x)
        (divisible? x y) (recur (/ x y) y (add y))
        :else (recur x (inc y) factors)))))

build.boot:

(set-env!
 :source-paths #{"src"}
 :dependencies '[[org.clojure/test.check "0.9.0" :scope "test"]])

(require '[clojure.spec.test :as stest]
         '[example.core :as core])

(deftask run []
  (with-pass-thru _
    (prn (stest/run-all-tests))))

Das erste Problem ist das offensichtlichste:

$ boot run
clojure.lang.ExceptionInfo: No such var: core/prime?
    data: {:file "example/spec.clj", :line 16}
java.lang.RuntimeException: No such var: core/prime?

In meiner Spezifikation fürfactor, Ich möchte mein @ verwendprime? Prädikat, um die zurückgegebenen Faktoren zu validieren. Das Coole an diesemfactor spec ist das, vorausgesetzt,prime? ist richtig, beides dokumentiert das @ vollständfactor Funktion und beseitigt die Notwendigkeit für mich, andere Tests für diese Funktion zu schreiben. Aber wenn Sie denken, dass das einfach zu cool ist, können Sie es durch @ ersetzepos? oder so

Unsurprisingly, thoug, h, du wirst immer noch eine Fehlermeldung erhalten, wenn du es versuchstboot run wieder dieses Mal beschweren, dass die:args spec für entweder#'example.core/divisible? oder#'example.core/prime? oder#'example.core/factor (je nachdem, was zuerst versucht wird) fehlt. Dies liegt daran, unabhängig davon, ob Siealias ein Namespace oder nicht,fdef werde nichtverwende dieser Alias, es sei denn, das Symbol, das Sie ihm geben, nennt eine Variable, dieist bereits vorhande. Wenn die Variable nicht existiert, wird das Symbol nicht erweitert. (Um noch mehr Spaß zu haben, entfernen Sie das:as core vonbuild.boot und sehen, was passiert.)

Wenn Sie diesen Alias behalten möchten, müssen Sie das @ entferne(:require [example.spec]) vonexample.core und füge ein @ hin(require 'example.spec) zubuild.boot. Natürlich, dassrequire muss kommennac der fürexample.core, oder es wird nicht funktionieren. Und an diesem Punkt, warum nicht einfach das @ setzrequire direkt inexample.spec?

Alle diese Probleme könnten gelöst werden, indem die Spezifikationen in dieselbe Datei wie die Implementierungen gestellt werden. Soll ich also Spezifikationen wirklich in andere Namespaces als Implementierungen stellen? Wenn ja, wie können die oben beschriebenen Probleme behoben werden?

Antworten auf die Frage(2)

Ihre Antwort auf die Frage