 (extend clojure.lang.IPersistentMap
   Shrink
   {:shrink shrink-map})
+
+(extend java.lang.Character
+  Shrink
+  {:shrink shrink-char})
+
+(extend java.lang.String
+  Shrink
+  {:shrink shrink-string})
+
+(extend clojure.lang.Keyword
+  Shrink
+  {:shrink (comp
+             (partial clojure.core/map clojure.core/keyword)
+             shrink-string
+             #(apply str (rest %))
+             str)})

  (shrink [this]))

(defn fmap
  [f gen]
  (fn [rand-seed size]
    (f (gen rand-seed size))))

                                      (choose 65 90)
                                      (choose 97 122)])))

(defn- stamp
  [c]
  [(not (Character/isLowerCase c))
   (not (Character/isUpperCase c))
   (not (Character/isDigit c))
   (not= \space c)
   c])

(defn- <-stamp
  [a b]
  (neg? (compare (stamp a) (stamp b))))

(defn shrink-char
  [c]
  (filter #(<-stamp % c)
          (concat [\a \b \c]
                  (for [x [c] :while #(Character/isUpperCase %)]
                    (Character/toLowerCase x))
                  [\A \B \C
                   \1 \2 \3
                   ;; TODO: should newline be here? It will make ascii chars
                   ;; shrink incorrectly. But it's also useful for finding bugs...
                   ;; \newline
                   \space])))

(defn string
  [rand-seed size]
  (apply str (repeatedly size #(char rand-seed size))))
