2016-06-22 14 views
3

Ich frage mich, wie ich eine Funktion spezifizieren würde, die einen Parameter hat, der eine Karte in einem Atom hält.Wie Clojure.Spec ein Referenztyp (wie Atom)?

(defn do-something [a] 
    (prn (vals @a))) 

Diese diejenigen arbeiten offenbar nicht:

(s/fdef do-something 
    :args (s/cat :a map?)) 

Wie würde ich spec dass a ein Hinweis auf der Karte ist?

Antwort

7

Sie nicht. Aus einem Grund wäre es nicht threadsicher. Wenn Sie irgendwie angegeben haben, dass ein Atom eine Karte enthält, könnte es sich in der Zeit, die Sie zur Überprüfung des Atoms benötigen, in eine Ganzzahl ändern und mit Ihrer Funktion fortfahren.

Eine Option besteht jedoch darin, dem Atom einen Validator zur Verfügung zu stellen. Sie könnten leicht partielle verwenden, um dies zu tun: (set-validator! my-atom (partial s/valid? :my-spec)). Jetzt kann das Atom nicht aktualisiert werden, es sei denn, der Wert entspricht: my-spec.

Eine weitere Option ist das Hinzufügen einer Validierungslogik zu allen Funktionen, die das Atom aktualisieren. Welcher dieser beiden Ansätze am besten funktioniert, hängt von der Anwendung ab.

+0

Ingenieur Best Practices für die nicht typisierte Sprachen Stirnrunzeln der Regel verschiedene Arten einer Variablen auf zuweisen. Eine solche Vorgehensweise führt oft zu Fehlern in einem System, beeinträchtigt die Lesbarkeit und wirkt sich negativ auf die Leistung aus. – Dylon

8

Nicht. clojure.spec beschreibt die Struktur von Daten und Atome sind Zustände, keine Daten. Nicht jede Funktion muss notwendigerweise eine Spezifikation haben (oder überprüfen).

Meine allgemeine Ratschläge für mit Stateful Daten zu tun ist:

  • Ihre Daten definieren
  • definieren reine Funktionen auf das nehmen und senden Sie Ihre Daten
  • erstellen Spezifikationen für die Datenfunktionen
  • Manipulieren Atome, die nur diese reinen Funktionen an so wenigen Orten wie möglich verwenden

Mit etwas Sorgfalt, Sie kann oft die Anzahl von Funktionen reduzieren, die Atome auf 0 bringen oder zurückführen (indem man das Atom an der Stelle schließt, an der es verwaltet wird), was ein würdiges Ziel ist.

+0

Wie wäre es mit der Spezifikation des Atoms (/ ref/agent) * selbst * für Einschränkungen? (Und wenn das möglich wäre - wie ich denke, sollte es auch sinnvoll sein, diese wenigen Funktionen anzugeben, die ein Atom aufnehmen oder zurückgeben). – pron

+0

@pron Ich denke, der Vorschlag von Timothy in der anderen Antwort über Validatoren ist der beste Weg, einen Referenztyp selbst zu spezifizieren. Ich denke, es ist in Ordnung, eine Funktion als eine '# (Instanz? Clojure.lang.IAtom%)' oder was auch immer. Aber nicht jede Funktion benötigt unbedingt eine Spezifikation. –

+0

Richtig, ich habe Validatoren vergessen. Es wäre schön, es strukturierter und idiomatischer zu gestalten. Ich stimme absolut zu, dass nicht alles eine Spezifikation benötigt, aber es ist gut, die volle Ausdrucksstärke zu haben, wenn du willst (in Zukunft könnte es mehr Werkzeuge geben, wie statische Analysen, die davon profitieren könnten). – pron

0

können Sie with-gen verwenden, ein eigenes Prädikat und einen benutzerdefinierten Generator:

(require '[clojure.spec.alpha :as spec] 
     '[clojure.spec.gen.alpha :as gen]) 

(defn memoize! [memo key distance] 
    "Memoizes the distance at the given key and returns the distance" 
    (swap! memo assoc key distance) 
    distance) 

(spec/def ::word 
    (spec/and string? (complement nil?))) 

(defn map-atom? [o] 
    (and (instance? clojure.lang.IAtom o) 
     (map? @o))) 

(def map-atom-gen 
    (gen/fmap 
    (fn [_] (atom {})) 
    (gen/int))) 

(spec/def ::map-atom 
    (spec/with-gen map-atom? 
    (constantly map-atom-gen))) 

(spec/fdef memoize! 
      :args (spec/tuple ::map-atom 
          (spec/coll-of ::word :type vector? :count 2) 
          nat-int?) 
      :ret nat-int? 
      :fn (fn [{[memo key distance] :args, retval :ret}] 
       (= distance (@memo key) retval)))