Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce an abstraction for enumerations with a semantically important Ord instance #549

Open
amesgen opened this issue Nov 3, 2022 · 0 comments

Comments

@amesgen
Copy link
Member

amesgen commented Nov 3, 2022

Often, the stock-derived Ord instance of a data type has no semantic meaning (i.e. it is just used to put them into Sets/Maps). In particular, one usually does not spend much thought on the order of constructors, even though it influences the derived Ord instance.

Hence, when the Ord instance does carry important semantic meaning (see IntersectMBO/ouroboros-network#3856 (comment) for an example), it would be nice to have a way to be very explicit about the ordering (that goes futher than a warning in the comments); in order to nudge someone modifying the data type in the future towards thinking about the implications for the Ord instance.

Ideally, this abstraction

  1. statically guarantees lawful instances,
  2. and has performance equal to handwritten code.

One possible approach might look like this: Instead of stock-deriving Ord/Enum/Bounded for a type Foo, the user implements

class FromEnum a where
  fromEnum_ :: a -> Int

for it, and uses the newtype OrdFromEnum

newtype OrdFromEnum a = OrdFromEnum a

instance (FromEnum a, ...) => Ord a where
  compare = comparing fromEnum

instance (FromEnum a, ...) => Enum a where
  fromEnum = fromEnum_
  toEnum   = ...

instance (...) => Bounded a where ...

(probably using GHC.Generics and an Array for toEnum for implementation) to allow users to write code such as

data Foo = A | B | C
  deriving stock (Eq, Show, Generic)
  deriving (Ord, Enum, Bounded) via OrdFromEnum Foo

instance FromEnum a where
  fromEnum_ = \case
    A -> 1
    B -> 2
    C -> 3

For lawfulness, it additionally requires a test that fromEnum_ is injective.

Note that this construction could also be split into two independent but composable parts (deriving fromEnum from toEnum or vice versa, and deriving Ord via fromEnum).

Other options include using a symmetric ToEnum-based approach or ordinary TH.

@dnadales dnadales transferred this issue from IntersectMBO/ouroboros-network Nov 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant