diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,13 @@
+## [0.2.14.0]
+
+* [Add `HashMap.mapKeys`.](https://github.com/haskell-unordered-containers/unordered-containers/pull/308) Thanks, Marco Perone!
+
+* [Add instances for `NFData1` and `NFData2`.](https://github.com/haskell-unordered-containers/unordered-containers/pull/314) Thanks, Isaac Elliott and Oleg Grenrus!
+
+* [Fix `@since`-annotation for `compose`.](https://github.com/haskell-unordered-containers/unordered-containers/pull/303) Thanks, @Mathnerd314!
+
+[0.2.14.0]: https://github.com/haskell-unordered-containers/unordered-containers/compare/v0.2.13.0...v0.2.14.0
+
 ## [0.2.13.0]
 
 * [Add `HashMap.compose`.](https://github.com/haskell-unordered-containers/unordered-containers/pull/299) Thanks Alexandre Esteves.
diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs
--- a/Data/HashMap/Internal.hs
+++ b/Data/HashMap/Internal.hs
@@ -67,6 +67,7 @@
     , map
     , mapWithKey
     , traverseWithKey
+    , mapKeys
 
       -- * Difference and intersection
     , difference
@@ -177,6 +178,10 @@
 import qualified Data.Hashable.Lifted as H
 #endif
 
+#if MIN_VERSION_deepseq(1,4,3)
+import qualified Control.DeepSeq as NF
+#endif
+
 #if __GLASGOW_HASKELL__ >= 802
 import GHC.Exts (TYPE, Int (..), Int#)
 #endif
@@ -200,6 +205,16 @@
 instance (NFData k, NFData v) => NFData (Leaf k v) where
     rnf (L k v) = rnf k `seq` rnf v
 
+#if MIN_VERSION_deepseq(1,4,3)
+-- | @since 0.2.14.0
+instance NFData k => NF.NFData1 (Leaf k) where
+    liftRnf rnf2 = NF.liftRnf2 rnf rnf2
+
+-- | @since 0.2.14.0
+instance NF.NFData2 Leaf where
+    liftRnf2 rnf1 rnf2 (L k v) = rnf1 k `seq` rnf2 v
+#endif
+
 -- Invariant: The length of the 1st argument to 'Full' is
 -- 2^bitsPerSubkey
 
@@ -222,6 +237,20 @@
     rnf (Full ary)            = rnf ary
     rnf (Collision _ ary)     = rnf ary
 
+#if MIN_VERSION_deepseq(1,4,3)
+-- | @since 0.2.14.0
+instance NFData k => NF.NFData1 (HashMap k) where
+    liftRnf rnf2 = NF.liftRnf2 rnf rnf2
+
+-- | @since 0.2.14.0
+instance NF.NFData2 HashMap where
+    liftRnf2 _ _ Empty                       = ()
+    liftRnf2 rnf1 rnf2 (BitmapIndexed _ ary) = NF.liftRnf (NF.liftRnf2 rnf1 rnf2) ary
+    liftRnf2 rnf1 rnf2 (Leaf _ l)            = NF.liftRnf2 rnf1 rnf2 l
+    liftRnf2 rnf1 rnf2 (Full ary)            = NF.liftRnf (NF.liftRnf2 rnf1 rnf2) ary
+    liftRnf2 rnf1 rnf2 (Collision _ ary)     = NF.liftRnf (NF.liftRnf2 rnf1 rnf2) ary
+#endif
+
 instance Functor (HashMap k) where
     fmap = map
 
@@ -1699,7 +1728,7 @@
 -- ('compose' bc ab '!?') = (bc '!?') <=< (ab '!?')
 -- @
 --
--- @since UNRELEASED
+-- @since 0.2.13.0
 compose :: (Eq b, Hashable b) => HashMap b c -> HashMap a b -> HashMap a c
 compose bc !ab
   | null bc = empty
@@ -1750,6 +1779,24 @@
     go (Collision h ary)     =
         Collision h <$> A.traverse' (\ (L k v) -> L k <$> f k v) ary
 {-# INLINE traverseWithKey #-}
+
+-- | /O(n)/.
+-- @'mapKeys' f s@ is the map obtained by applying @f@ to each key of @s@.
+--
+-- The size of the result may be smaller if @f@ maps two or more distinct
+-- keys to the same new key. In this case there is no guarantee which of the
+-- associated values is chosen for the conflicting key.
+--
+-- >>> mapKeys (+ 1) (fromList [(5,"a"), (3,"b")])
+-- fromList [(4,"b"),(6,"a")]
+-- >>> mapKeys (\ _ -> 1) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")])
+-- fromList [(1,"c")]
+-- >>> mapKeys (\ _ -> 3) (fromList [(1,"b"), (2,"a"), (3,"d"), (4,"c")])
+-- fromList [(3,"c")]
+--
+-- @since 0.2.14.0
+mapKeys :: (Eq k2, Hashable k2) => (k1 -> k2) -> HashMap k1 v -> HashMap k2 v
+mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) []
 
 ------------------------------------------------------------------------
 -- * Difference and intersection
diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs
--- a/Data/HashMap/Internal/Array.hs
+++ b/Data/HashMap/Internal/Array.hs
@@ -75,7 +75,7 @@
 import Control.Applicative (Applicative (..), (<$>))
 #endif
 import Control.Applicative (liftA2)
-import Control.DeepSeq
+import Control.DeepSeq (NFData (..))
 import GHC.Exts(Int(..), Int#, reallyUnsafePtrEquality#, tagToEnum#, unsafeCoerce#, State#)
 import GHC.ST (ST(..))
 import Control.Monad.ST (stToIO)
@@ -104,6 +104,10 @@
 import qualified Prelude
 #endif
 
+#if MIN_VERSION_deepseq(1,4,3)
+import qualified Control.DeepSeq as NF
+#endif
+
 import Data.HashMap.Internal.Unsafe (runST)
 import Control.Monad ((>=>))
 
@@ -249,6 +253,22 @@
 -- We use index# just in case GHC can't see that the
 -- relevant rnf is strict, or in case it actually isn't.
 {-# INLINE rnfArray #-}
+
+#if MIN_VERSION_deepseq(1,4,3)
+-- | @since 0.2.14.0
+instance NF.NFData1 Array where
+    liftRnf = liftRnfArray
+
+liftRnfArray :: (a -> ()) -> Array a -> ()
+liftRnfArray rnf0 ary0 = go ary0 n0 0
+  where
+    n0 = length ary0
+    go !ary !n !i
+        | i >= n = ()
+        | (# x #) <- index# ary i
+        = rnf0 x `seq` go ary n (i+1)
+{-# INLINE liftRnfArray #-}
+#endif
 
 -- | Create a new mutable array of specified size, in the specified
 -- state thread, with each element containing the specified initial
diff --git a/Data/HashMap/Internal/Strict.hs b/Data/HashMap/Internal/Strict.hs
--- a/Data/HashMap/Internal/Strict.hs
+++ b/Data/HashMap/Internal/Strict.hs
@@ -81,6 +81,7 @@
     , map
     , mapWithKey
     , traverseWithKey
+    , mapKeys
 
       -- * Difference and intersection
     , difference
diff --git a/Data/HashMap/Lazy.hs b/Data/HashMap/Lazy.hs
--- a/Data/HashMap/Lazy.hs
+++ b/Data/HashMap/Lazy.hs
@@ -66,6 +66,7 @@
     , map
     , mapWithKey
     , traverseWithKey
+    , mapKeys
 
       -- * Difference and intersection
     , difference
diff --git a/Data/HashMap/Strict.hs b/Data/HashMap/Strict.hs
--- a/Data/HashMap/Strict.hs
+++ b/Data/HashMap/Strict.hs
@@ -65,6 +65,7 @@
     , map
     , mapWithKey
     , traverseWithKey
+    , mapKeys
 
       -- * Difference and intersection
     , difference
diff --git a/Data/HashSet/Internal.hs b/Data/HashSet/Internal.hs
--- a/Data/HashSet/Internal.hs
+++ b/Data/HashSet/Internal.hs
@@ -123,6 +123,10 @@
 import qualified Data.Hashable.Lifted as H
 #endif
 
+#if MIN_VERSION_deepseq(1,4,3)
+import qualified Control.DeepSeq as NF
+#endif
+
 import Data.Functor ((<$))
 
 -- | A set of values.  A set cannot contain duplicate values.
@@ -137,6 +141,12 @@
 instance (NFData a) => NFData (HashSet a) where
     rnf = rnf . asMap
     {-# INLINE rnf #-}
+
+#if MIN_VERSION_deepseq(1,4,3)
+-- | @since 0.2.14.0
+instance NF.NFData1 HashSet where
+    liftRnf rnf1 = NF.liftRnf2 rnf1 rnf . asMap
+#endif
 
 -- | Note that, in the presence of hash collisions, equal @HashSet@s may
 -- behave differently, i.e. substitutivity may be violated:
diff --git a/tests/HashMapProperties.hs b/tests/HashMapProperties.hs
--- a/tests/HashMapProperties.hs
+++ b/tests/HashMapProperties.hs
@@ -301,6 +301,9 @@
   L.sort (fmap (L.sort . M.toList) (M.traverseWithKey (\_ v -> [v + 1, v + 2]) (M.fromList (take 10 xs))))
      == L.sort (fmap (L.sort . HM.toList) (HM.traverseWithKey (\_ v -> [v + 1, v + 2]) (HM.fromList (take 10 xs))))
 
+pMapKeys :: [(Int, Int)] -> Bool
+pMapKeys = M.mapKeys (+1) `eq_` HM.mapKeys (+1)
+
 ------------------------------------------------------------------------
 -- ** Difference and intersection
 
@@ -504,6 +507,7 @@
     -- Transformations
     , testProperty "map" pMap
     , testProperty "traverse" pTraverse
+    , testProperty "mapKeys" pMapKeys
     -- Folds
     , testGroup "folds"
       [ testProperty "foldr" pFoldr
diff --git a/unordered-containers.cabal b/unordered-containers.cabal
--- a/unordered-containers.cabal
+++ b/unordered-containers.cabal
@@ -1,5 +1,5 @@
 name:           unordered-containers
-version:        0.2.13.0
+version:        0.2.14.0
 synopsis:       Efficient hashing-based container types
 description:
   Efficient hashing-based container types.  The containers have been
@@ -22,8 +22,9 @@
 extra-source-files: CHANGES.md
 
 tested-with:
-  GHC ==8.10.1
-   || ==8.8.3
+  GHC ==9.0.1
+   || ==8.10.4
+   || ==8.8.4
    || ==8.6.5
    || ==8.4.4
    || ==8.2.2
