
Hi Mark, On Wed, 2025-03-26 at 21:37 +0000, Mark McConnell via Haskell-Cafe wrote:
instance Lex2 a => Ord a where compare x y = comp1 x y <> comp2 x y
This attempts to define `Ord a` in general, for *every* type `a` at once. The `Lex2 a` constraint is only checked *after* the compiler has decided this is the instance to use. Besides decidability concerns, this is probably not what you want, since it overlaps with every other type's Ord instance. If you want to express a strategy for implementing one typeclass in terms of others, I would suggest using a newtype together with DerivingVia: newtype UsePrimaryAndSecondary a = MkUsePrimaryAndSecondary a instance (Primary a, Secondary a) => Ord (Lex2 a) where compare (MkLex2 x) (MkLex2 y) = comp1 x y <> comp2 x y data Foo = -- ... deriving Ord via (UsePrimaryAndSecondary Foo) instance Primary Foo where -- ... instance Secondary Foo where -- ... If, on the other hand, you want to express a constraint, as opposed to an implementation strategy, I think the conventional thing to do would be to define Lex2 as a subclass of Ord, Primary, and Secondary, and no additional members. Then put the (informal) requirements in a comment, similar to e.g. the monad laws. You can of course combine the two approaches.
I feel I must be missing something. UndecidableInstances seems too extreme for what I am trying to do. (I have never said that I want to go backwards in class inference from Ord to Lex2.)
This is, in general, how type class resolution works. The compiler matches on the *head* (the thing to the right of =>), and then recurses on the constraints (to the left of the =>). -- Jade