diff --git a/edges.cabal b/edges.cabal
--- a/edges.cabal
+++ b/edges.cabal
@@ -1,5 +1,5 @@
 name: edges
-version: 0.9.0.1
+version: 0.9.0.2
 category: Graphs
 synopsis: Tools for efficient immutable graphs
 description: A set of tools for constructing immutable graphs which are both memory and performance-efficient.
diff --git a/library/Edges/Functions.hs b/library/Edges/Functions.hs
--- a/library/Edges/Functions.hs
+++ b/library/Edges/Functions.hs
@@ -69,5 +69,10 @@
 nodeCountsUnboxedVector (NodeCounts pa) = PrimArray.toUnboxedVector pa
 
 unindexNodeCounts :: (Eq entity, Hashable entity) => (Int -> Maybe entity) -> NodeCounts entity -> HashMap entity Int
-unindexNodeCounts lookup (NodeCounts pa) =
-  Unfold.fold (Foldl.hashMapByKeyLookup lookup) (Unfold.intsInRange 0 (pred (sizeofPrimArray pa)))
+unindexNodeCounts lookup (NodeCounts pa) = let
+  unfold = do
+    index <- Unfold.intsInRange 0 (pred (sizeofPrimArray pa))
+    return $ do
+      entity <- lookup index
+      return (entity, fromIntegral (indexPrimArray pa index))
+  in Unfold.fold (Foldl.hashMapByMapMaybe id) unfold
diff --git a/library/Edges/Functions/Folds.hs b/library/Edges/Functions/Folds.hs
--- a/library/Edges/Functions/Folds.hs
+++ b/library/Edges/Functions/Folds.hs
@@ -7,6 +7,9 @@
 import qualified Data.HashMap.Strict as HashMap
 
 
+hashMapByMapMaybe :: (Eq b, Hashable b) => (a -> Maybe (b, c)) -> Fold a (HashMap b c)
+hashMapByMapMaybe mapMaybe = lmap mapMaybe (handles _Just hashMap)
+
 hashMapByKeyLookup :: (Eq b, Hashable b) => (a -> Maybe b) -> Fold a (HashMap b a)
 hashMapByKeyLookup lookup = Fold (flip updateMap) init extract where
   updateMap a = case lookup a of
diff --git a/library/Edges/Prelude.hs b/library/Edges/Prelude.hs
--- a/library/Edges/Prelude.hs
+++ b/library/Edges/Prelude.hs
@@ -6,6 +6,16 @@
   forMInAscendingRange_,
   forMInDescendingRange_,
   (.),
+  showText,
+  -- * Optics
+  Lens,
+  Lens',
+  Prism,
+  Prism',
+  lens,
+  prism,
+  _Left,
+  _Just,
 )
 where
 
@@ -156,3 +166,33 @@
 forMInDescendingRange_ :: Applicative m => Int -> Int -> (Int -> m a) -> m ()
 forMInDescendingRange_ !startN !endN f =
   ($ pred startN) $ fix $ \loop !n -> if n >= endN then f n *> loop (pred n) else pure ()
+
+showText = fromString . show
+
+
+-- * Optics
+-------------------------
+
+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
+
+type Lens' s a = Lens s s a a
+
+type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
+
+type Prism' s a = Prism s s a a
+
+{-# INLINE lens #-}
+lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
+lens sa sbt afb s = sbt s <$> afb (sa s)
+
+{-# INLINE prism #-}
+prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
+prism bt seta = dimap seta (either pure (fmap bt)) . right'
+
+{-# INLINE _Left #-}
+_Left :: Prism' (Either a b) a
+_Left = prism Left (either Right (Left . Right))
+
+{-# INLINE _Just #-}
+_Just :: Prism' (Maybe a) a
+_Just = prism Just (maybe (Left Nothing) Right)
