    the keyword `:constraints`.
   "
   [n docstring & constraints]
-  (let [raw-cnstr   (partition 2 constraints)
-        arity-cnstr (for [[a c] raw-cnstr]
-                      (build-constraints-map a c))
-        fn-arities  (for [b arity-cnstr]
-                      (build-contract docstring b))
-        body (list* 'fn n fn-arities)]
-    `(with-meta 
-       ~body
-       {:constraints (into {} '~arity-cnstr)
-        :docstring ~docstring})))
+  (if (not (string? docstring))
+    (throw (IllegalArgumentException. "Sorry, but contracts require docstrings"))
+    (let [raw-cnstr   (partition 2 constraints)
+          arity-cnstr (for [[a c] raw-cnstr]
+                        (build-constraints-map a c))
+          fn-arities  (for [b arity-cnstr]
+                        (build-contract docstring b))
+          body (list* 'fn n fn-arities)]
+      `(with-meta 
+         ~body
+         {:constraints (into {} '~arity-cnstr)
+          :docstring ~docstring}))))
 
 (defn with-constraints
   "A contract combinator.


(defn- build-pre-post-map
  "Takes a vector of the form `[pre ... => post ...]` and infers the expectations described
   therein.  The map that comes out will look like Clojure's default pre- and post-conditions
   map.
  "
  [cnstr]
  (if (vector? cnstr)

           (= t# (type r#))))

       (let [chk# (contract ~(symbol (str "chk-" name))
                            ~(str "Invariant contract for " name) 
                            [{:keys ~fields :as m#}] ~invariants)]
         (defconstrainedfn ~factory-name
           ([] [] (with-meta 
