diff --git a/benchmark/Main.lhs b/benchmark/Main.lhs
--- a/benchmark/Main.lhs
+++ b/benchmark/Main.lhs
@@ -3,6 +3,7 @@
 > import Algorithms.Lloyd.Sequential (Point(..), Cluster(..))
 > import qualified Algorithms.Lloyd.Sequential as Sequential (kmeans)
 > import qualified Algorithms.Lloyd.Strategies as Strategies (kmeans)
+> import Data.Metric (Metric(..), Euclidean(..))
 > import Data.Random.Normal (mkNormals)
 > import Data.Vector (fromList)
 > import Control.Monad (forM)
@@ -34,6 +35,6 @@
  
 > main :: IO ()
 > main = defaultMain
->   [ bench "Sequential" $ nf (Sequential.kmeans points) clusters
->   , bench "Strategies" $ nf (Strategies.kmeans 64 points) clusters
+>   [ bench "Sequential" $ nf (Sequential.kmeans Euclidean points) clusters
+>   , bench "Strategies" $ nf (Strategies.kmeans Euclidean 64 points) clusters
 >   ] 
diff --git a/kmeans-par.cabal b/kmeans-par.cabal
--- a/kmeans-par.cabal
+++ b/kmeans-par.cabal
@@ -1,5 +1,5 @@
 name:                kmeans-par
-version:             1.0.2
+version:             1.1.0
 synopsis:            Sequential and parallel implementations of Lloyd's algorithm.
 license:             MIT
 license-file:        LICENSE
@@ -12,7 +12,9 @@
 library
   exposed-modules:     Algorithms.Lloyd.Sequential,
                        Algorithms.Lloyd.Strategies
-  build-depends:       base == 2.*, vector, semigroups, parallel, split
+  other-modules:       Data.Functor.Extras
+  other-extensions:    ViewPatterns 
+  build-depends:       base == 4.*, vector, semigroups, parallel, split, metric
   hs-source-dirs:      src
   ghc-options:         -threaded
   default-language:    Haskell2010
@@ -21,6 +23,6 @@
   type:             exitcode-stdio-1.0
   main-is:          Main.lhs
   hs-source-dirs:   benchmark
-  build-depends:    base == 2.*, random, criterion, normaldistribution, kmeans-par, deepseq, vector
+  build-depends:    base == 4.*, random, criterion, normaldistribution, kmeans-par, deepseq, vector, metric
   ghc-options:      -threaded
   default-language: Haskell2010
diff --git a/src/Algorithms/Lloyd/Sequential.lhs b/src/Algorithms/Lloyd/Sequential.lhs
--- a/src/Algorithms/Lloyd/Sequential.lhs
+++ b/src/Algorithms/Lloyd/Sequential.lhs
@@ -1,3 +1,5 @@
+> {-# LANGUAGE ViewPatterns #-}
+
 A sequential implementation of Lloyd's algorithm for k-means clustering,
 adapted from Marlow's _Parallel and Concurrent Programming in Haskell_:
 
@@ -6,43 +8,38 @@
 > import Prelude hiding (zipWith, map, foldr1, replicate)
 > import Control.Monad (guard, forM_)
 > import Data.Function (on)
+> import Data.Functor.Extras ((..:),(...:))
 > import Data.List (minimumBy)
+> import Data.Metric (Metric(..))
 > import Data.Semigroup (Semigroup(..))
 > import Data.Vector (Vector(..), toList, create, zipWith, map, foldr1, empty, replicate)
 > import qualified Data.Vector.Mutable as MV (replicate, read, write)
  
 A data point is represented by an n-dimensional real vector:
 
-> data Point = Point (Vector Double) deriving (Eq, Show)
+> data Point = Point 
+>   { point :: Vector Double
+>   } deriving (Eq, Show)
 
 Point isn't parametrised, so we can't define a functor (or any functor-family)
 instances. Instead, we define a specialised map, `pmap`:
 
 > pmap :: (Double -> Double) -> Point -> Point
-> pmap f (Point vector) = Point $ map f vector
- 
-The `Metric` typeclass, as defined here, is intended to contain types that
-admit metrics (i.e. are metric spaces.) Instances can be defined in terms of 
-`distance` or the infix `<->`:
-
-> class Metric a where
->   distance :: a -> a -> Double
->   distance = (<->)
->   (<->) :: a -> a -> Double
->   (<->) = distance
+> pmap f = Point . map f . point
 
-We define distance between data-points as the sum of squares of differences of
-corresponding coordinates. The actual (Euclidean) distance is given by the
-square root of this value, but as we're only interested in comparing distances,
-we omit that function for efficiency:
+To define distance between data-points, we admit arbitrary metric spaces
+over real vectors; **However:** it should be noted that convergence is
+only guaranteed in the case when the mean of the metric optimises the
+same criterion as minimum-distance assignment (`assign` below.) For this
+reason, Euclidean-distance is popularly used for mean assignment.
 
-> instance Metric Point where
->   Point v0 <-> Point v1 = foldr1 (+) . map (**2) $ zipWith (-) v0 v1
+> useMetric :: Metric a => (Vector Double -> a) -> Point -> Point -> Double
+> useMetric metric = distance `on` (metric . point)
  
 `Point`s may be combined using simple vector addition:
 
 > instance Semigroup Point where
->   Point v0 <> Point v1 = Point $ zipWith (+) v0 v1
+>   (<>) = Point ..: zipWith (+) `on` point
 
 Clusters are represented by an identifier and a centroid:
 
@@ -54,7 +51,7 @@
 Clusters are treated as equivalent if they share a centroid:
 
 > instance Eq Cluster where
->   (Cluster _ c) == (Cluster _ d) = c == d
+>   (==) = (==) `on` centroid
 
 `PointSum` is a point sum; this is an intermediate type used to contain the sum
 of points in a set when constructing new clusters. It consists of a count of
@@ -86,7 +83,7 @@
 > toCluster :: Int -> PointSum -> Cluster
 > toCluster cid (PointSum count point) = Cluster
 >   { identifier = cid
->   , centroid   = pmap (//count) point
+>   , centroid   = pmap (// count) point
 >   }
 >
 > (//) :: Double -> Int -> Double
@@ -95,16 +92,16 @@
 After each iteration, points are associated with the cluster having the nearest
 centroid:
 
-> closestCluster :: [Cluster] -> Point -> Cluster
-> closestCluster clusters point = fst . minimumBy (compare `on` snd) $ do
+> closestCluster :: Metric a => (Vector Double -> a) -> [Cluster] -> Point -> Cluster
+> closestCluster (useMetric -> d) clusters point = fst . minimumBy (compare `on` snd) $ do
 >   cluster <- clusters
->   return (cluster, point <-> centroid cluster)
+>   return (cluster, point `d` centroid cluster)
 >
-> assign :: [Cluster] -> [Point] -> Vector PointSum
-> assign clusters points = let nc = length clusters in create $ do
+> assign :: Metric a => (Vector Double -> a) -> [Cluster] -> [Point] -> Vector PointSum
+> assign metric clusters points = let nc = length clusters in create $ do
 >   vector <- MV.replicate nc $ emptyPointSum nc
 >   points `forM_` \point -> do
->     let cluster  = closestCluster clusters point 
+>     let cluster  = closestCluster metric clusters point 
 >         position = identifier cluster
 >     sum <- MV.read vector position
 >     MV.write vector position $! addPoint sum point
@@ -118,28 +115,24 @@
 >                     -- computing the centroid.
 >   return $ toCluster index pointSum
 >
-> step :: [Cluster] -> [Point] -> [Cluster]
-> step = makeNewClusters ..: assign
+> step :: Metric a => (Vector Double -> a) -> [Cluster] -> [Point] -> [Cluster]
+> step = makeNewClusters ...: assign
 >
-> (..:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
-> (..:) = fmap . fmap
-> 
-> infixr 8 ..:
 
 The algorithm consists of iteratively finding the centroid of each existing
 cluster, then reallocating points according to which centroid is closest, until
 convergence. As the algorithm isn't guaranteed to converge, we cut execution if
 convergence hasn't been observed after eighty iterations:
 
-> kmeans :: [Point] -> [Cluster] -> [Cluster]
-> kmeans = kmeans' 0
+> kmeans :: Metric a => (Vector Double -> a) -> [Point] -> [Cluster] -> [Cluster]
+> kmeans = flip kmeans' 0 
 >
-> kmeans' :: Int -> [Point] -> [Cluster] -> [Cluster]
-> kmeans' iterations points clusters 
+> kmeans' :: Metric a => (Vector Double -> a) -> Int -> [Point] -> [Cluster] -> [Cluster]
+> kmeans' metric iterations points clusters 
 >   | iterations >= expectDivergent = clusters
 >   | clusters' == clusters         = clusters 
->   | otherwise                     = kmeans' (succ iterations) points clusters'
->   where clusters' = step clusters points
+>   | otherwise                     = kmeans' metric (succ iterations) points clusters'
+>   where clusters' = step metric clusters points
 >
 > expectDivergent :: Int
 > expectDivergent = 80
diff --git a/src/Algorithms/Lloyd/Strategies.lhs b/src/Algorithms/Lloyd/Strategies.lhs
--- a/src/Algorithms/Lloyd/Strategies.lhs
+++ b/src/Algorithms/Lloyd/Strategies.lhs
@@ -7,10 +7,12 @@
 >
 > import Prelude hiding (zipWith)
 > import Control.Parallel.Strategies (Strategy(..), parTraversable, using, rseq)
+> import Data.Functor.Extras ((..:))
 > import Data.List.Split (chunksOf)
+> import Data.Metric (Metric(..))
 > import Data.Semigroup (Semigroup(..))
 > import Data.Vector (Vector(..), zipWith)
-> import Algorithms.Lloyd.Sequential (Cluster(..), Point(..), PointSum(..), makeNewClusters, assign, (..:), expectDivergent)
+> import Algorithms.Lloyd.Sequential (Cluster(..), Point(..), PointSum(..), makeNewClusters, assign, expectDivergent)
 
 We can combine two vectors of some same type $t$ provided we know how to
 combine two $t$s:
@@ -21,8 +23,8 @@
 Step is modified to, given a partitioned list of points, perform
 classification in parallel:
 
-> step :: [Cluster] -> [[Point]] -> [Cluster]
-> step = makeNewClusters . foldr1 (<>) . with (parTraversable rseq) ..: map . assign 
+> step :: Metric a => (Vector Double -> a) -> [Cluster] -> [[Point]] -> [Cluster]
+> step = makeNewClusters . foldr1 (<>) . with (parTraversable rseq) ..: map ..: assign 
 >
 > with :: Strategy a -> a -> a
 > with = flip using
@@ -36,12 +38,12 @@
 are too few items, and those items vary in cost, some of our cores may
 be unused for part of the computation.
 
-> kmeans :: Int -> [Point] -> [Cluster] -> [Cluster]
-> kmeans = kmeans' 0 ..: chunksOf
+> kmeans :: Metric a => (Vector Double -> a) -> Int -> [Point] -> [Cluster] -> [Cluster]
+> kmeans metric = kmeans' metric 0  ..: chunksOf
 >
-> kmeans' :: Int -> [[Point]] -> [Cluster] -> [Cluster]
-> kmeans' iterations points clusters 
+> kmeans' :: Metric a => (Vector Double -> a) -> Int -> [[Point]] -> [Cluster] -> [Cluster]
+> kmeans' metric iterations points clusters 
 >   | iterations >= expectDivergent = clusters
 >   | clusters' == clusters         = clusters 
->   | otherwise                     = kmeans' (succ iterations) points clusters'
->   where clusters' = step clusters points
+>   | otherwise                     = kmeans' metric (succ iterations) points clusters'
+>   where clusters' = step metric clusters points
diff --git a/src/Data/Functor/Extras.hs b/src/Data/Functor/Extras.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Functor/Extras.hs
@@ -0,0 +1,13 @@
+module Data.Functor.Extras (
+  (..:),
+  (...:)
+) where
+
+(..:) :: (Functor f, Functor g) => (a -> b) -> f(g(a)) -> f(g(b))
+(..:) = fmap fmap fmap
+infixl 4 ..:
+
+(...:) :: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))
+(...:) = fmap . fmap . fmap
+
+infixr 4 ...:
