diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2015, Ricky Elrod
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/ms.cabal b/ms.cabal
new file mode 100644
--- /dev/null
+++ b/ms.cabal
@@ -0,0 +1,66 @@
+name:                ms
+version:             0.1
+synopsis:            metric spaces
+description:
+  A 'MetricSpace' is a set together with a notion of distance between
+  elements. Distance is computed by a function 'dist' which has the following
+  four laws:
+  .
+    (1) __non-negative__: @forall x y. 'dist' x y >= 0@
+    (2) __identity of indiscernibles__: @forall x y. 'dist' x y == 0 \<=\> x == y@
+    (3) __symmetry__: @forall x y. dist x y == 'dist' y x@
+    (4) __triangle inequality__: @forall x y z. 'dist' x z <= 'dist' x y + 'dist' y z@
+  .
+  See the Wikipedia <https://en.wikipedia.org/wiki/Metric_space article on
+  metric spaces> for more details.
+
+homepage:            https://github.com/relrod/ms
+license:             BSD2
+license-file:        LICENSE
+author:              Ricky Elrod, Tony Morris
+maintainer:          ricky@elrod.me
+copyright:           (C) 2015 Ricky Elrod, Tony Morris
+category:            Math
+build-type:          Simple
+cabal-version:       >= 1.10
+
+source-repository head
+  type:     git
+  location: https://github.com/relrod/ms
+
+library
+  exposed-modules:
+    Math.MetricSpace
+  build-depends:       base >= 4 && < 5
+                     , contravariant >= 1 && < 2
+                     , edit-distance >= 0.2 && < 0.3
+                     , lens >= 4 && < 5
+                     , profunctors >= 5 && < 6
+                     , semigroups >= 0.12 && < 0.17
+                     , vector >= 0.10 && < 0.12
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite test
+  default-language:    Haskell2010
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      tests
+  main-is:             test.hs
+  build-depends:       base >= 4 && < 5
+                     , tasty >= 0.10
+                     , tasty-quickcheck >= 0.8
+                     , ms
+                     , profunctors >= 5 && < 6
+                     , vector >= 0.10 && < 0.12
+  ghc-options:         -threaded
+                       -O2
+                       "-with-rtsopts=-N"
+
+test-suite doctests
+  default-language:    Haskell2010
+  type:                exitcode-stdio-1.0
+  ghc-options:        -threaded
+  hs-source-dirs:      tests
+  main-is:             doctests.hs
+  build-depends:       base >= 4 && < 5
+                     , doctest >= 0.8
diff --git a/src/Math/MetricSpace.hs b/src/Math/MetricSpace.hs
new file mode 100644
--- /dev/null
+++ b/src/Math/MetricSpace.hs
@@ -0,0 +1,189 @@
+{-# LANGUAGE CPP #-}
+
+#ifndef MIN_VERSION_base
+#define MIN_VERSION_base(x,y,z) 1
+#endif
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Math.MetricSpace
+-- Copyright   :  (C) 2015 Ricky Elrod, Tony Morris
+-- License     :  BSD2 (see LICENSE file)
+--
+-- Maintainer  :  Ricky Elrod <ricky@elrod.me>
+-- Stability   :  provisional
+-- Portability :  lens
+--
+-- A 'MetricSpace' is a set together with a notion of distance between
+-- elements. Distance is computed by a function 'dist' which has the following
+-- four laws:
+--
+--   (1) __non-negative__: @forall x y. 'dist' x y >= 0@
+--   (2) __identity of indiscernibles__: @forall x y. 'dist' x y == 0 \<=\> x == y@
+--   (3) __symmetry__: @forall x y. dist x y == 'dist' y x@
+--   (4) __triangle inequality__: @forall x y z. 'dist' x z <= 'dist' x y + 'dist' y z@
+--
+-- See the Wikipedia <https://en.wikipedia.org/wiki/Metric_space article on
+-- metric spaces> for more details.
+----------------------------------------------------------------------------
+module Math.MetricSpace where
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative
+#endif
+
+import Control.Lens
+import Data.Function (on)
+import Data.Functor.Contravariant.Divisible
+import Data.Profunctor
+import Data.Semigroup
+import qualified Data.Vector as V
+import Text.EditDistance
+
+-- $setup
+-- >>> import qualified Data.Vector as V
+
+newtype MetricSpace a b = MetricSpace { dist :: a -> a -> b }
+
+type ClosedMetricSpace a = MetricSpace a a
+newtype FlippedMetricSpace b a = FlippedMetricSpace (MetricSpace a b)
+type ClosedFlippedMetricSpace a = FlippedMetricSpace a a
+
+-- TODO: How to make this useful? Right now the precedence is all wrong.
+infixl 8 <->
+(<->) :: MetricSpace a b -> a -> a -> b
+(<->) = dist
+{-# INLINE (<->) #-}
+
+instance Functor (MetricSpace a) where
+  fmap = dimap id
+  {-# INLINE fmap #-}
+
+instance Applicative (MetricSpace a) where
+  pure = MetricSpace . const . const
+  {-# INLINE pure #-}
+  MetricSpace f <*> MetricSpace a = MetricSpace (\x y -> f x y (a x y))
+  {-# INLINE (<*>) #-}
+
+instance Monad (MetricSpace a) where
+  return = pure
+  {-# INLINE return #-}
+  MetricSpace f >>= fn =
+    MetricSpace (\x y -> let MetricSpace s =  fn (f x y) in s x y)
+  {-# INLINE (>>=) #-}
+
+instance Semigroup b => Semigroup (MetricSpace a b) where
+  MetricSpace m1 <> MetricSpace m2 =
+    MetricSpace (\a1 a2 -> m1 a1 a2 <> m2 a1 a2)
+  {-# INLINE (<>) #-}
+
+instance Monoid b => Monoid (MetricSpace a b) where
+  mempty = MetricSpace . const . const $ mempty
+  {-# INLINE mempty #-}
+  mappend (MetricSpace m1) (MetricSpace m2) =
+    MetricSpace (\a1 a2 -> m1 a1 a2 `mappend` m2 a1 a2)
+  {-# INLINE mappend #-}
+
+instance Profunctor MetricSpace where
+  lmap f (MetricSpace b) = MetricSpace (b `on` f)
+  {-# INLINE lmap #-}
+
+  rmap f (MetricSpace b) = MetricSpace (\x y -> f (b x y))
+  {-# INLINE rmap #-}
+
+_FlippedMetricSpace
+  :: Iso
+     (MetricSpace a b)
+     (MetricSpace x y)
+     (FlippedMetricSpace b a)
+     (FlippedMetricSpace y x)
+_FlippedMetricSpace = iso FlippedMetricSpace (\(FlippedMetricSpace m) -> m)
+{-# INLINE _FlippedMetricSpace #-}
+
+instance Contravariant (FlippedMetricSpace b) where
+  contramap f (FlippedMetricSpace m) = FlippedMetricSpace (dimap f id m)
+  {-# INLINE contramap #-}
+
+instance Monoid b => Divisible (FlippedMetricSpace b) where
+  divide
+    f
+    (FlippedMetricSpace (MetricSpace m1))
+    (FlippedMetricSpace (MetricSpace m2)) =
+      FlippedMetricSpace (MetricSpace (\a1 a2 -> let (b1, c1) = f a1
+                                                     (b2, c2) = f a2
+                                                 in
+                                                   m1 b1 b2 `mappend` m2 c1 c2))
+  {-# INLINE divide #-}
+  conquer = FlippedMetricSpace . MetricSpace . const . const $ mempty
+  {-# INLINE conquer #-}
+
+class SwappedMetricSpace m where
+  _SwappedMetricSpace :: Iso (m a b) (m x y) (m a b) (m x y)
+
+instance SwappedMetricSpace MetricSpace where
+  _SwappedMetricSpace =
+    iso
+      (\(MetricSpace m) -> MetricSpace (\a1 a2 -> m a2 a1))
+      (\(MetricSpace m) -> MetricSpace (\a2 a1 -> m a1 a2))
+  {-# INLINE _SwappedMetricSpace #-}
+
+instance SwappedMetricSpace FlippedMetricSpace where
+  _SwappedMetricSpace =
+    iso
+      (\(FlippedMetricSpace (MetricSpace m)) ->
+         FlippedMetricSpace (MetricSpace (\a1 a2 -> m a2 a1)))
+      (\(FlippedMetricSpace (MetricSpace m)) ->
+         FlippedMetricSpace (MetricSpace (\a1 a2 -> m a2 a1)))
+  {-# INLINE _SwappedMetricSpace #-}
+
+-- | Levenshtein distance between 'String's.
+--
+-- >>> dist levenshtein "foo" "bar"
+-- 3.0
+--
+-- >>> dist levenshtein "hi" "ha"
+-- 1.0
+--
+-- >>> dist levenshtein "ff" "ff"
+-- 0.0
+levenshtein :: Floating b => MetricSpace String b
+levenshtein =
+  MetricSpace (\a b -> fromIntegral $ levenshteinDistance defaultEditCosts a b)
+{-# INLINE levenshtein #-}
+
+-- | Discrete distance over n-dimensional 'Vector's.
+--
+-- >>> dist discrete (V.fromList [3,4]) (V.fromList [3,4])
+-- 0.0
+--
+-- >>> dist discrete (V.fromList [1,49]) (V.fromList [3,-94])
+-- 1.0
+discrete :: (Eq a, Floating b) => MetricSpace (V.Vector a) b
+discrete = MetricSpace (\a b -> if a == b then 0 else 1)
+{-# INLINE discrete #-}
+
+-- | Euclidean distance over n-dimensional 'Vector's.
+--
+-- >>> dist euclidean (V.fromList [3,4]) (V.fromList [3,4])
+-- 0.0
+--
+-- >>> dist euclidean (V.fromList [1,49]) (V.fromList [3,-94])
+-- 143.01398533010678
+euclidean :: RealFloat a => MetricSpace (V.Vector a) a
+euclidean = MetricSpace (\a b -> f a b `seq` sqrt (f a b))
+  where
+    f a b = V.sum (V.zipWith (\x y -> (x-y)^2) a b)
+{-# INLINE euclidean #-}
+
+-- | Taxicab distance over n-dimensional 'Vector's.
+--
+-- >>> dist taxicab (V.fromList [3,4]) (V.fromList [3,4])
+-- 0.0
+--
+-- >>> dist taxicab (V.fromList [1,49]) (V.fromList [3,-94])
+-- 145.0
+taxicab :: RealFloat a => MetricSpace (V.Vector a) a
+taxicab = MetricSpace f
+  where
+    f a b = V.sum (V.zipWith (\x y -> abs (x-y)) a b)
+{-# INLINE taxicab #-}
diff --git a/tests/doctests.hs b/tests/doctests.hs
new file mode 100644
--- /dev/null
+++ b/tests/doctests.hs
@@ -0,0 +1,2 @@
+import Test.DocTest
+main = doctest ["-isrc", "src/Math/MetricSpace.hs"]
diff --git a/tests/test.hs b/tests/test.hs
new file mode 100644
--- /dev/null
+++ b/tests/test.hs
@@ -0,0 +1,85 @@
+import Data.Profunctor
+import qualified Data.Vector as V
+import Math.MetricSpace
+import Test.Tasty
+import Test.Tasty.QuickCheck as QC
+
+data SameLengthVector3 a = SameLengthVector3 (V.Vector a) (V.Vector a) (V.Vector a) deriving Show
+
+main = defaultMain $ opts tests
+  where
+    opts = localOption (QuickCheckTests 3000)
+
+tests :: TestTree
+tests = testGroup "Tests" [properties]
+
+properties :: TestTree
+properties = testGroup "Properties" [qcProps]
+
+nonnegative :: (Num b, Ord b) => MetricSpace a b -> a -> a -> Bool
+nonnegative m a b = dist m a b >= 0
+
+indisc :: (Eq b, Num b) => MetricSpace a b -> a -> Bool
+indisc m a = dist m a a == 0
+
+symmetry :: Eq b => MetricSpace a b -> a -> a -> Bool
+symmetry m a b = dist m a b == dist m b a
+
+triangle :: (Num b, Ord b) => MetricSpace a b -> a -> a -> a -> Bool
+triangle m a b c = dist m a c <= dist m a b + dist m b c
+
+instance (Arbitrary a) => Arbitrary (V.Vector a) where
+  arbitrary = fmap V.fromList arbitrary
+
+triangleVect
+  :: (Num b, Ord b, Show a, Arbitrary a) =>
+     MetricSpace (V.Vector a) b -> V.Vector a -> Property
+triangleVect m xs =
+  forAll (vectorOf (V.length xs) arbitrary) $ \ys ->
+  forAll (vectorOf (V.length xs) arbitrary) $ \zs -> triangle m xs (V.fromList ys) (V.fromList zs)
+
+genTestGroup
+  :: (Num b, Ord b, Show a, Arbitrary a, Eq a) =>
+     TestName -> MetricSpace a b -> TestTree
+genTestGroup name metric =
+  testGroup name [
+        QC.testProperty "nonnegative" $ \a b -> nonnegative metric a b
+      , QC.testProperty "indisc" $ \a -> indisc metric a
+      , QC.testProperty "symmetry" $ \a b -> symmetry metric a b
+      , QC.testProperty "triangle" $ \a b -> triangle metric a b
+      , QC.testProperty "profunctor lmap" $ \a b -> dist (lmap id metric) a b == dist metric a b
+      , QC.testProperty "profunctor rmap" $ \a b -> dist (rmap id metric) a b == dist metric a b
+    ]
+
+genTestGroupV
+  :: (Num b, Ord b, Show a, Arbitrary a, Eq a) =>
+     TestName -> MetricSpace (V.Vector a) b -> TestTree
+genTestGroupV name metric =
+  testGroup name [
+        QC.testProperty "nonnegative" $ \a b -> nonnegative metric a b
+      , QC.testProperty "indisc" $ \a -> indisc metric a
+      , QC.testProperty "symmetry" $ \a b -> symmetry metric a b
+      -- TODO:
+      --, QC.testProperty "triangle" $ triangleVect metric
+      , QC.testProperty "profunctor lmap" $ \a b -> dist (lmap id metric) a b == dist metric a b
+      , QC.testProperty "profunctor rmap" $ \a b -> dist (rmap id metric) a b == dist metric a b
+    ]
+
+qcProps = testGroup "(checked by QuickCheck)"
+  [
+    genTestGroup "levenshtein" levenshtein
+
+    -- Double
+  , testGroup "Double" [
+        genTestGroupV "discrete" (discrete :: MetricSpace (V.Vector Double) Double)
+      , genTestGroupV "euclidean" (euclidean :: MetricSpace (V.Vector Double) Double)
+      , genTestGroupV "taxicab" (taxicab :: MetricSpace (V.Vector Double) Double)
+    ]
+
+    -- Float
+  , testGroup "Float" [
+        genTestGroupV "discrete" (discrete :: MetricSpace (V.Vector Float) Float)
+      , genTestGroupV "euclidean" (euclidean :: MetricSpace (V.Vector Float) Float)
+      , genTestGroupV "taxicab" (taxicab :: MetricSpace (V.Vector Float) Float)
+    ]
+  ]
