External Publication
Visit Post

Lawvere theories

Haskell Community [Unofficial] April 17, 2026
Source

I’ve also been thinking about this! In the end I settled on using the value level and concrete Bools for testing purposes. The Variety class actually closely resembles your original Lawvere but with some minor modifications to increase flexibility.

class Variety (var :: Type -> Constraint) where
  type Requirements var x :: Constraint
  data Signature var :: Type -> Type
  operations :: (var x, Requirements var x) => Signature var x -> x
  data Laws var :: Type -> Type
  lawful :: (var x, Requirements var x) => Laws var x -> Bool

For instance,

data EqLaw x
  = EqLawReflexive x
  | EqLawSymmetric x x
  | EqLawTransitive x x x
  deriving (Eq, Ord, Show)

instance Variety Eq where
  type Requirements Eq x = ()
  data Signature Eq x
  operations :: (Eq x) => Signature Eq x -> x
  operations = \case {}
  newtype Laws Eq x = EqLaw (EqLaw x)
    deriving (Show)
  lawful :: (Eq x) => Laws Eq x -> Bool
  lawful = \case
    EqLaw (EqLawReflexive x) -> x == x
    EqLaw (EqLawSymmetric x y) -> (x == y) --> (y == x)
    EqLaw (EqLawTransitive x y z) -> (x == y && y == z) --> (x == z)

data OrdLaw x
  = OrdLawReflexive x
  | OrdLawAntisymmetric x x
  | OrdLawTransitive x x x
  deriving (Eq, Ord, Show)

instance Variety Ord where
  data Signature Ord x
  operations :: (Ord x) => Signature Ord x -> x
  operations = \case {}
  type Requirements Ord x = ()
  newtype Laws Ord x = OrdLaw (OrdLaw x)
    deriving (Show)
  lawful :: (Ord x) => Laws Ord x -> Bool
  lawful = \case
    OrdLaw (OrdLawReflexive x) -> x <= x
    OrdLaw (OrdLawAntisymmetric x y) -> (x <= y && y <= x) --> (x == y)
    OrdLaw (OrdLawTransitive x y z) -> (x <= y && y <= z) --> (x <= z)

instance Variety Semigroup where
  type Requirements Semigroup x = Eq x
  data Signature Semigroup x
    = SemigroupAppend x x
    deriving (Show)
  operations ::
    (Semigroup x, Requirements Semigroup x) =>
    Signature Semigroup x -> x
  operations = \case
    SemigroupAppend x y -> x <> y
  data Laws Semigroup x
    = SemigroupAppendAssociative x x x
    deriving (Show)
  lawful ::
    (Semigroup x, Requirements Semigroup x) =>
    Laws Semigroup x -> Bool
  lawful = \case
    SemigroupAppendAssociative x y z -> x <> (y <> z) == (x <> y) <> z

instance Variety Monoid where
  type Requirements Monoid x = Eq x
  data Signature Monoid x
    = MonoidSemigroup (Signature Semigroup x)
    | MonoidMempty
    deriving (Show)
  operations ::
    (Monoid x, Requirements Monoid x) =>
    Signature Monoid x -> x
  operations = \case
    MonoidSemigroup sig -> operations sig
    MonoidMempty -> mempty
  data Laws Monoid x
    = MonoidSemigroupLaws (Laws Semigroup x)
    | MonoidMemptyLeft x
    | MonoidMemptyRight x
    deriving (Show)
  lawful ::
    (Monoid x, Requirements Monoid x) =>
    Laws Monoid x -> Bool
  lawful = \case
    MonoidSemigroupLaws laws -> lawful laws
    MonoidMemptyLeft x -> mempty <> x == x
    MonoidMemptyRight x -> x <> mempty == x

I’ve also been working with an extended algebraic hierarchy with Variety instances for them. Additive groups, semirings, rings, fields, modules and vector spaces, all expressible as Varietys. It also lets one model the type of “arity” directly as a Haskell datatype instead of having to (or getting to, depending on your perspective ) define the arities explicitly. This makes it easy to derive Generic for Laws var x, giving light work to the deriving strategies for test generators. Generatable boilerplate below:

instance (Eq x, Arbitrary x) => Arbitrary (EqLaw x) where
  arbitrary :: Gen (EqLaw x)
  arbitrary =
    oneof
      [ EqLawReflexive <$> arbitrary
      , EqLawSymmetric <$> arbitrary <*> arbitrary
      , EqLawTransitive <$> arbitrary <*> arbitrary <*> arbitrary
      ]

instance (Eq x, Arbitrary x) => Arbitrary (Laws Eq x) where
  arbitrary :: Gen (Laws Eq x)
  arbitrary = EqLaw <$> arbitrary

instance (Eq x, Arbitrary x) => Arbitrary (OrdLaw x) where
  arbitrary :: Gen (OrdLaw x)
  arbitrary =
    oneof
      [ OrdLawReflexive <$> arbitrary
      , OrdLawAntisymmetric <$> arbitrary <*> arbitrary
      , OrdLawTransitive <$> arbitrary <*> arbitrary <*> arbitrary
      ]

instance (Eq x, Arbitrary x) => Arbitrary (Laws Ord x) where
  arbitrary :: Gen (Laws Ord x)
  arbitrary = OrdLaw <$> arbitrary

instance (Eq x, Arbitrary x) => Arbitrary (Laws Semigroup x) where
  arbitrary :: Gen (Laws Semigroup x)
  arbitrary = SemigroupAppendAssociative <$> arbitrary <*> arbitrary <*> arbitrary

instance (Eq x, Arbitrary x) => Arbitrary (Laws Monoid x) where
  arbitrary :: Gen (Laws Monoid x)
  arbitrary = MonoidSemigroupLaws <$> arbitrary

Discussion in the ATmosphere

Loading comments...