         (= arg-attrs ret-attrs))))
 
 (s/fdef operation/with-attributes
-  :args (s/cat :operation ::delta/operation :attributes (s/nilable (s/map-of keyword? any?)))
+  :args (s/cat :operation ::delta/operation :attributes (s/nilable attributes))
   :ret ::delta/operation
   :fn (s/and operation-type-preserved?
              operation-contents-preserved?
              attributes-set?))
 
 (s/fdef operation/insert
-  :args (s/cat :val (s/alt :text string? :embed ::delta/embed)
-               :attributes (s/? (s/nilable (s/map-of keyword? any?))))
-  :ret (s/nilable (s/and ::delta/operation
-                         #(::delta/insert %)))
+  :args (s/cat :val (s/alt :text string? :embed embed)
+               :attributes (s/? (s/nilable attributes)))
+  :ret (s/nilable (s/and ::delta/operation (has-type ::delta/insert)))
   :fn (s/or :nil (s/and #(-> % :args :val val (= ""))
                         #(-> % :ret nil?))
             :non-nil (s/and #(-> % :args :val val (= "") not)
                             attributes-set?
                             #(= (-> % :args :val val)
                                 ((-> % :args :val key)
-                                  (->> % :ret ::delta/insert (apply hash-map)))))))
+                                 (->> % :ret ::delta/insert (apply hash-map)))))))
+
+(s/fdef operation/retain
+  :args (s/cat :n int?
+               :attributes (s/? (s/nilable attributes)))
+  :ret (s/nilable (s/and ::delta/operation (has-type ::delta/retain)))
+  :fn (s/or :nil (s/and #(-> % :args :n pos? not)
+                        #(-> % :ret nil?))
+            :non-nil (s/and #(-> % :args :n pos?)
+                            attributes-set?
+                            #(= (-> % :args :n)
+                                (-> % :ret conformed-operation-length)))))
+
+(defn- nil-keys
+  "Given a map `m`, return a sequence of those keys that are bound to a
+  `nil` value."
+  [m]
+  (into [] (comp (filter (comp nil? val)) (map key)) m))
+
+(defn- dissoc-all
+  "Returns a copy of `map` with the `keys` removed."
+  [map keys]
+  (apply dissoc map keys))
+
+(defn- apply-attribute-diff
+  "Given a map of `attributes` and a `diff`, apply the `diff` to the
+  `attributes` and return the result.
+  "
+  [attributes diff]
+  (-> attributes
+      (merge diff)
+      (dissoc-all (nil-keys diff))))
+
+(s/fdef operation/attribute-diff
+  :args (s/cat :attributes (s/nilable (s/and attributes #(every? (comp some? val) %)))
+               :other-attributes (s/nilable (s/and attributes #(every? (comp some? val) %))))
+  :ret attributes
+  :fn (s/and #(every? (partial contains? (-> % :args :attributes)) (-> % :ret nil-keys))
+             #(= (-> % :args :other-attributes (or {}))
+                 (-> % :args :attributes (apply-attribute-diff (:ret %))))))

  :ret #{::delta/insert ::delta/delete ::delta/retain})

(defn- operation-type-preserved?
  ""
  [{:keys [args ret]}]
  (= (-> ret operation/type)
     (-> args :operation operation/type)))

(defn- attributes-preserved?
  ""
  [{:keys [args ret]}]
  (= (-> ret ::delta/attributes)
     (-> args :operation ::delta/attributes)))

(defn- length-clipped-to-n?
  ""
  [{:keys [args ret]}]
  (= (-> ret unconform-operation operation/length)
     (min (:n args) (-> args :operation unconform-operation operation/length))))

(defn- text-preserved?
  ""
  [relation {:keys [args ret]}]
  (let [insert-text (some->> args :operation ::delta/insert (apply hash-map) :text)]
    (or (nil? insert-text)

(s/fdef operation/drop
  :args (s/cat :n int? :operation ::delta/operation)
  :ret (s/nilable ::delta/operation)
  :fn (s/or :nil (s/and #(>= (-> % :args :n) (-> % :args :operation conformed-operation-length))
                        #(-> % :ret nil?))
            :non-nil (s/and #(< (-> % :args :n) (-> % :args :operation conformed-operation-length))
                            operation-type-preserved?
                            attributes-preserved?
                            length-shortened-by-n?
                            #(text-preserved? string/ends-with? %))))

(defn- has-type
  ""
  [type]
  #(type %))

(s/fdef operation/delete
  :args (s/cat :n int?)
  :ret (s/nilable (s/and ::delta/operation (has-type ::delta/delete)))
  :fn (s/or :nil (s/and #(-> % :args :n pos? not)
                        #(-> % :ret nil?))
            :non-nil (s/and #(-> % :args :n pos?)
                            #(= (-> % :args :n)
                                (-> % :ret conformed-operation-length)))))

(defn- operation-contents-preserved?
  "True if the instrumented function returned an operation with the same
  contents as the function’s `operation` argument. For delete and retain
  operations, the contents is simply the operation’s length. For insert
  operations, the contents is the text or embed.
  "
  [{:keys [args ret]}]
  (let [type (operation/type ret)]
    (= (type ret)
       (-> args :operation type))))

(defn- attributes-set?
  "True if the instrumented function returned an operation with the same
  attributes as the function’s `attributes` argument.
  "
  [{:keys [args ret]}]
  (let [arg-attrs (-> args :attributes)
        ret-attrs (-> ret ::delta/attributes)]
