diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog for `partialord`
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to the
+[Haskell Package Versioning Policy](https://pvp.haskell.org/).
+
+## Unreleased
+
+## 0.1.0.0 - YYYY-MM-DD
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2023
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+# partialord
+
+A data structure supporting partial orders, and some helpful
+associated routines.
+
+## Other packages
+
+An alternative package is already available, namely
+  [partial-order](https://hackage.haskell.org/package/partial-order-0.2.0.0/docs/Data-PartialOrd.html).
+Differences include:
+* PartialOrd has a comparison valued in Maybe Ordering; we use a fresh
+  type.
+* Where types have several natural partial orderings, we provide
+  newtypes rather than choosing one.
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/partialord.cabal b/partialord.cabal
new file mode 100644
--- /dev/null
+++ b/partialord.cabal
@@ -0,0 +1,59 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.36.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           partialord
+version:        0.0.0
+synopsis:       Data structure supporting partial orders
+description:    Please see README.md
+category:       Data structures
+homepage:       https://github.com/jcranch/partialord#readme
+bug-reports:    https://github.com/jcranch/partialord/issues
+author:         James Cranch
+maintainer:     j.d.cranch@sheffield.ac.uk
+copyright:      2023 James Cranch
+license:        BSD-3-Clause
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/jcranch/partialord
+
+library
+  exposed-modules:
+      Data.PartialOrd
+  other-modules:
+      Paths_partialord
+  autogen-modules:
+      Paths_partialord
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
+  build-depends:
+      base >=4.7 && <5
+    , containers ==0.6.*
+  default-language: Haskell2010
+
+test-suite partialord-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Data.PartialOrdSpec
+      Paths_partialord
+  autogen-modules:
+      Paths_partialord
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , containers ==0.6.*
+    , hspec >=2.7 && <2.12
+    , partialord
+  default-language: Haskell2010
diff --git a/src/Data/PartialOrd.hs b/src/Data/PartialOrd.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/PartialOrd.hs
@@ -0,0 +1,277 @@
+{-# LANGUAGE
+      DerivingVia,
+      PatternSynonyms,
+      StandaloneDeriving
+  #-}
+
+-- | Partial orders
+module Data.PartialOrd (
+  -- * Comparisons in partial orders
+  PartialOrdering(..),
+  fromOrd,
+  toMaybeOrd,
+  fromMaybeOrd,
+  fromLeqGeq,
+  -- * Partial orderings
+  PartialOrd(..),
+  comparable,
+  -- * Special partial orderings
+  FullyOrd(..),
+  Discrete(..),
+  -- * Maxima and minima
+  Maxima(..),
+  maxima,
+  Minima(..),
+  minima,
+  -- * Partial orders on lists
+  Infix(..),
+  Prefix(..),
+  Suffix(..),
+  Subseq(..),
+  ) where
+
+import Data.IntSet (IntSet)
+import qualified Data.IntSet as IS
+import Data.List (isInfixOf, isPrefixOf, isSuffixOf, isSubsequenceOf)
+import Data.Monoid ()
+import Data.Semigroup ()
+import Data.Set (Set)
+import qualified Data.Set as S
+
+
+-- | A data type representing relationships between two objects in a
+-- poset: they can be related (by EQ', LT' or GT'; like EQ, LT or GT),
+-- or unrelated (NT').
+data PartialOrdering = EQ' | LT' | GT' | NT'
+  deriving (Eq, Show)
+
+-- | Convert an ordering into a partial ordering
+fromOrd :: Ordering -> PartialOrdering
+fromOrd EQ = EQ'
+fromOrd LT = LT'
+fromOrd GT = GT'
+
+-- | Convert a partial ordering to an ordering
+toMaybeOrd :: PartialOrdering -> Maybe Ordering
+toMaybeOrd EQ' = Just EQ
+toMaybeOrd LT' = Just LT
+toMaybeOrd GT' = Just GT
+toMaybeOrd NT' = Nothing
+
+-- | Convert an ordering into a partial ordering
+fromMaybeOrd :: Maybe Ordering -> PartialOrdering
+fromMaybeOrd (Just EQ) = EQ'
+fromMaybeOrd (Just LT) = LT'
+fromMaybeOrd (Just GT) = GT'
+fromMaybeOrd Nothing   = NT'
+
+-- | Convert from `leq` and `geq` to a partial ordering
+fromLeqGeq :: Bool -> Bool -> PartialOrdering
+fromLeqGeq True True = EQ'
+fromLeqGeq True False = LT'
+fromLeqGeq False True = GT'
+fromLeqGeq False False = NT'
+
+
+-- | A helper type for constructing partial orderings from total
+-- orderings (using deriving via)
+newtype FullyOrd a = FullyOrd {
+  getOrd :: a
+} deriving (Eq, Ord, Show)
+
+instance (Ord a) => PartialOrd (FullyOrd a) where
+  compare' (FullyOrd x) (FullyOrd y) = fromOrd $ compare x y
+
+
+-- | A helper type for constructing partial orderings where everything
+-- is equal or incomparable.
+newtype Discrete a = Discrete {
+  getDiscrete :: a
+} deriving (Eq, Show)
+
+instance (Eq a) => PartialOrd (Discrete a) where
+  compare' (Discrete x) (Discrete y)
+    | x == y    = EQ'
+    | otherwise = NT'
+
+
+-- | A comparison (less than or equal, greater than or equal) holds if
+-- and only if it does on both arguments.
+instance Semigroup PartialOrdering where
+  NT' <> _   = NT'
+  EQ' <> x   = x
+  _   <> NT' = NT'
+  x   <> EQ' = x
+  LT' <> LT' = LT'
+  GT' <> GT' = GT'
+  _   <> _   = NT'
+
+instance Monoid PartialOrdering where
+  mempty = EQ'
+
+-- | A typeclass expressing partially ordered types: any two elements
+-- are related by a `PartialOrdering`.
+class PartialOrd a where
+  {-# MINIMAL compare' | leq #-}
+
+  compare' :: a -> a -> PartialOrdering
+  compare' a b = fromLeqGeq (a `leq` b) (a `geq` b)
+
+  leq :: a -> a -> Bool
+  a `leq` b = case compare' a b of
+    LT' -> True
+    EQ' -> True
+    _   -> False
+
+  geq :: a -> a -> Bool
+  a `geq` b = b `leq` a
+
+-- | Are they LT', EQ', GT'
+comparable :: PartialOrd a => a -> a -> Bool
+comparable a b = case compare' a b of
+  NT' -> False
+  _   -> True
+
+-- | It's hard to imagine another sensible instance
+deriving via FullyOrd Int instance PartialOrd Int
+
+-- | It's hard to imagine another sensible instance
+deriving via FullyOrd Integer instance PartialOrd Integer
+
+
+instance PartialOrd () where
+  compare' _ _ = EQ'
+
+-- | This is equivalent to
+--
+--   >   compare' (a,b) (c,d) = compare' a c <> compare' b d
+--
+--   but may be more efficient: if compare' a1 a2 is LT' or GT' we seek less
+--   information about b1 and b2 (and this can be faster).
+instance (PartialOrd a, PartialOrd b) => PartialOrd (a,b) where
+  compare' (a1,b1) (a2,b2) = case compare' a1 a2 of
+    NT' -> NT'
+    EQ' -> compare' b1 b2
+    LT' -> if b1 `leq` b2 then LT' else NT'
+    GT' -> if b1 `geq` b2 then GT' else NT'
+  (a1,b1) `leq` (a2,b2) = a1 `leq` a2 && b1 `leq` b2
+
+instance (PartialOrd a, PartialOrd b, PartialOrd c) => PartialOrd (a,b,c) where
+  compare' (a1,b1,c1) (a2,b2,c2) = compare' ((a1,b1),c1) ((a2,b2),c2)
+  (a1,b1,c1) `leq` (a2,b2,c2) = a1 `leq` a2 && b1 `leq` b2 && c1 `leq` c2
+
+instance (PartialOrd a, PartialOrd b, PartialOrd c, PartialOrd d) => PartialOrd (a,b,c,d) where
+  compare' (a1,b1,c1,d1) (a2,b2,c2,d2) = compare' (((a1,b1),c1),d1) (((a2,b2),c2),d2)
+  (a1,b1,c1,d1) `leq` (a2,b2,c2,d2) = a1 `leq` a2 && b1 `leq` b2 && c1 `leq` c2 && d1 `leq` d2
+
+instance (PartialOrd a, PartialOrd b, PartialOrd c, PartialOrd d, PartialOrd e) => PartialOrd (a,b,c,d,e) where
+  compare' (a1,b1,c1,d1,e1) (a2,b2,c2,d2,e2) = compare' ((((a1,b1),c1),d1),e1) ((((a2,b2),c2),d2),e2)
+  (a1,b1,c1,d1,e1) `leq` (a2,b2,c2,d2,e2) = a1 `leq` a2 && b1 `leq` b2 && c1 `leq` c2 && d1 `leq` d2 && e1 `leq` e2
+
+
+instance Ord a => PartialOrd (Set a) where
+  leq = S.isSubsetOf
+
+  compare' u v = case compare (S.size u) (S.size v) of
+    LT -> if S.isSubsetOf u v then LT' else NT'
+    GT -> if S.isSubsetOf v u then GT' else NT'
+    EQ -> if u == v then EQ' else NT'
+
+instance PartialOrd IntSet where
+  leq = IS.isSubsetOf
+
+  compare' u v = case compare (IS.size u) (IS.size v) of
+    LT -> if IS.isSubsetOf u v then LT' else NT'
+    GT -> if IS.isSubsetOf u v then GT' else NT'
+    EQ -> if u == v then EQ' else NT'
+
+
+-- | Lists partially ordered by infix inclusion
+newtype Infix a = Infix {
+  unInfix :: [a]
+} deriving (Eq, Show)
+
+instance Eq a => PartialOrd (Infix a) where
+  Infix a `leq` Infix b = isInfixOf a b
+
+-- | Lists partially ordered by prefix inclusion
+newtype Prefix a = Prefix {
+  unPrefix :: [a]
+} deriving (Eq, Show)
+
+instance Eq a => PartialOrd (Prefix a) where
+  compare' (Prefix a) (Prefix b) = let
+    inner [] [] = EQ'
+    inner [] _ = LT'
+    inner _ [] = GT'
+    inner (x:xs) (y:ys)
+      | x == y    = inner xs ys
+      | otherwise = NT'
+    in inner a b
+  Prefix a `leq` Prefix b = isPrefixOf a b
+
+
+-- | Lists partially ordered by suffix inclusion
+newtype Suffix a = Suffix {
+  unSuffix :: [a]
+} deriving (Eq, Show)
+
+instance Eq a => PartialOrd (Suffix a) where
+  Suffix a `leq` Suffix b = isSuffixOf a b
+
+
+-- | Lists partially ordered by the subsequence relation
+newtype Subseq a = Subseq {
+  unSubseq :: [a]
+} deriving (Eq, Show)
+
+instance Eq a => PartialOrd (Subseq a) where
+  Subseq a `leq` Subseq b = isSubsequenceOf a b
+
+
+-- | Sets of incomparable elements, with a monoidal structure obtained
+-- by taking the maximal ones.
+--
+-- Unfortunately, we need a full ordering for these to work (since
+-- they use sets), though we don't assume this ordering has any
+-- compatibility with the partial order. The monoid structures are
+-- most efficient with pre-reduced sets as the left-hand argument.
+newtype Maxima a = Maxima {
+  maxSet :: Set a
+}
+
+instance (Ord a, PartialOrd a) => Semigroup (Maxima a) where
+  Maxima s1 <> Maxima s2 = let
+    noLarger s x = not . any ((== LT') . compare' x) $ S.toList s
+    s2' = S.filter (noLarger s1) s2
+    s1' = S.filter (noLarger s2') s1
+    in Maxima $ S.union s1' s2'
+
+instance (Ord a, PartialOrd a) => Monoid (Maxima a) where
+  mempty = Maxima S.empty
+  mappend = (<>)
+
+-- | Find the maxima of a list (passing it through the machinery above)
+maxima :: (Ord a, PartialOrd a) => [a] -> [a]
+maxima = S.toList . maxSet . mconcat . fmap (Maxima . S.singleton)
+
+
+-- | As above, but with minima
+newtype Minima a = Minima {
+  minSet :: Set a
+}
+
+instance (Ord a, PartialOrd a) => Semigroup (Minima a) where
+  Minima s1 <> Minima s2 = let
+    noSmaller s x = not . any ((== GT') . compare' x) $ S.toList s
+    s2' = S.filter (noSmaller s1) s2
+    s1' = S.filter (noSmaller s2') s1
+    in Minima $ S.union s1' s2'
+
+instance (Ord a, PartialOrd a) => Monoid (Minima a) where
+  mempty = Minima S.empty
+  mappend = (<>)
+
+-- | Find the minima of a list (passing it through the machinery above)
+minima :: (Ord a, PartialOrd a) => [a] -> [a]
+minima = S.toList . minSet . mconcat . fmap (Minima . S.singleton)
diff --git a/test/Data/PartialOrdSpec.hs b/test/Data/PartialOrdSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Data/PartialOrdSpec.hs
@@ -0,0 +1,19 @@
+module Data.PartialOrdSpec (spec) where
+
+import Test.Hspec
+import Data.PartialOrd
+
+
+spec :: Spec
+spec = do
+
+  describe "Maxima and minima" $ do
+
+    it "should compute maxima" $ do
+      maxima [(i,j) | i <- [1..10], j <- [1..10], i+j <= 10] `shouldBe`
+        [(i :: Int,10-i) | i <- [1..9]]
+
+    it "should compute minima" $ do
+      minima [(i,j) | i <- [1..10], j <- [1..10], i+j >= 10] `shouldBe`
+        [(i :: Int,10-i) | i <- [1..9]]
+
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
