Skip to content

Instantly share code, notes, and snippets.

@n2o
Created July 7, 2017 21:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save n2o/215f75e98a4fe2ee7b03cf0c5cc5c397 to your computer and use it in GitHub Desktop.
Save n2o/215f75e98a4fe2ee7b03cf0c5cc5c397 to your computer and use it in GitHub Desktop.
Clojure Meetup July: clojure.spec

We played around with clojure.spec and followed the post Interactive Development with clojure.spec.

In the beginning, we started to get to know the semantics of clojure.spec and looked at some specs. After this, we solved the codebreaker-example and successfully defined some specs for it.

REPL Session from Clojure Meetup Düsseldorf, Germany, 6th July 2017

(ns codebreaker.core
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.test.alpha :as stest]
            [clojure.spec.gen.alpha :as gen]))

;; First steps with specs. And additional examples / code snippets to explain
;; the behavior of Clojure.

(use 'clojure.repl)

(doc defn)

(string? 42)
(int? 42)

(doc string?)
(source string?)

(s/valid? string? 42)

(s/conform string? "foo")
(s/conform keyword? :clojure.spec.alpha/invalid)

(s/explain-data string? 42)

(s/def ::even-pos
  (s/and even? nat-int?))

(s/conform ::even-pos 1)
(s/explain ::even-pos -1)
(s/valid? ::even-pos 2)

(::even-pos 2)

(s/def ::startfoo #(.startsWith % "foo"))
(s/explain ::startfoo "bar")

(s/fdef bruch
        :args (s/cat :zaehler int? :nenner (s/and int? #(not (zero? %))))
        :ret (s/or :ratio? ratio? :int int?)
        :fn (fn [{:keys [args ret]}]
              (println {:args args
                        :ret (second ret)
                        :prod (*' (:zaehler args) (:nenner args))})
              (and (nat-int? (*' (:zaehler args) (:nenner args)))
                     (nat-int? (second ret)))))
(stest/summarize-results (stest/check `bruch))


;; Explaining destructuring
(defn foo [{:keys [bar baz]}]
  [bar baz])
(defn foo' [coll]
  (let [bar (:bar coll)
        baz (:baz coll)]
    [baz baz]))
(foo {:bar "bar" :baz "baz"})
(foo' {:bar "bar" :baz "baz"})


(defn bruch [z n]
  (/ z n))

(bruch "foo" 2)
(stest/instrument `bruch)
(s/exercise (:args (s/get-spec `bruch)))
(s/conform (:args (s/get-spec `bruch)) [2 2])





;; Codebreaker!
;; From http://blog.cognitect.com/blog/2016/10/5/interactive-development-with-clojurespec

;; "We want a function that accepts a secret code and a guess, and returns a
;; score for that guess. Codes are made of 4 to 6 colored pegs, selected from
;; six colors: [r]ed, [y]ellow, [g]reen, [c]yan, [b]lack, and [w]hite. The score
;; is based on the number of pegs in the guess that match the secret code. A peg
;; in the guess that matches the color of the peg in the same position in the
;; secret code is considered an exact match, and a peg that matches a peg in a
;; different position in the secret code is considered a loose match.
;;
;; For example, if the secret code is [:r :y :g :c] and the guess
;; is [:c :y :g :b], the score would be {:codebreaker/exact-matches
;; 2 :codebreaker/loose-matches 1} because :y and :g appear in the same
;; positions and :c appears in a different position."

(def pegs #{:r :y :g :c :b :w})

(s/def ::code (s/coll-of pegs :min-count 4 :max-count 6))
(s/def ::exact-matches nat-int?)
(s/def ::loose-matches nat-int?)

(s/def ::secret-and-guess
  (s/and (s/cat :secret ::code :guess ::code)
         (fn [{:keys [secret guess]}]
           (= (count secret) (count guess)))))

(s/fdef score
        :args ::secret-and-guess
        :ret (s/keys :req [::exact-matches ::loose-matches])
        :fn #(<= (apply + (-> % :ret vals)) (-> % :args :secret count)))
(s/exercise ::secret-and-guess)

(s/def ::matches-bounds
  #(<= 0 (:ret %) (-> % :args :secret count)))

(s/fdef exact-matches
        :args ::secret-and-guess
        :ret nat-int?
        :fn ::matches-bounds)
(s/fdef loose-matches
        :args ::secret-and-guess
        :ret nat-int?
        :fn ::matches-bounds)

(defn exact-matches [secret guess]
  (count (filter true? (map = secret guess))))

(defn all-matches [secret guess]
  (let [secret-freq (select-keys (frequencies secret) guess)
        guess-freq (select-keys (frequencies guess) secret)]
    (apply + (vals (merge-with min secret-freq guess-freq)))))

(def secret [:y :g :y :w :g])
(def guess  [:g :y :w :w :b])
(all-matches secret guess)
[(select-keys (frequencies secret) guess)
 (select-keys (frequencies guess) secret)]

(stest/instrument `all-matches)

(defn score [secret guess]
  (let [exact (exact-matches secret guess)]
    {::exact-matches exact
     ::loose-matches (- (all-matches secret guess) exact)}))
(stest/summarize-results (stest/check `score))
(s/exercise-fn `score)


;; Specs now appear in the docs
(doc score)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment