GHC infuses Generic in such a way that variants L and R form a tree in which the leaves are in Enum order. Consider the following example (with cropped output):
ghci> data D = A | B | C | D | E deriving (Generic) ghci> from A L1 (L1 U1) ghci> from B L1 (R1 U1) ghci> from C R1 (L1 U1) ghci> from D R1 (R1 (L1 U1)) ghci> from E R1 (R1 (R1 U1)))
Note that if you placed them as a tree, toEnum `map` [1..] will be the left and right leaf toEnum `map` [1..] . With this intuition, we start by defining the GLeaves class, which counts the number of leaves that have a common type (not value!) In its tree.
{-# LANGUAGE ScopedTypeVariables, PolyKinds, TypeApplications, TypeOperators, DefaultSignatures, FlexibleContexts, TypeFamilies #-} import GHC.Generics import Data.Proxy class GLeaves f where -- | Counts the number of "leaves" (ie U1's) in the type `f` gSize :: Proxy f -> Int instance GLeaves U1 where gSize _ = 1 instance GLeaves x => GLeaves (M1 itx) where gSize _ = gSize (Proxy :: Proxy x) instance (GLeaves x, GLeaves y) => GLeaves (x :+: y) where gSize _ = gSize (Proxy :: Proxy x) + gSize (Proxy :: Proxy y)
Now we have a form for defining GEnum . As usual in this setup, we define our Enum' class and have default signatures that rely on GEnum .
class Enum' a where toEnum' :: Int -> a fromEnum' :: a -> Int default toEnum' :: (Generic a, GEnum (Rep a)) => Int -> a toEnum' = to . gToEnum default fromEnum' :: (Generic a, GEnum (Rep a)) => a -> Int fromEnum' = gFromEnum . from class GEnum f where gFromEnum :: fp -> Int gToEnum :: Int -> fp
Finally, we get to the good. For U1 and M1 , gFromEnum and gToEnum are simple. For :+: gFromEnum needs to find all the leaves to the left of it, so if this is the correct subtree, we add the size of the left subtree (and if it is the left subtree, we do not add anything). Similarly, gToEnum checks to see if it belongs in the left or right subtree, checking if it is less than the number of leaves in the left subtree.
instance GEnum U1 where gFromEnum U1 = 0 gToEnum n = if n == 0 then U1 else error "Outside enumeration range" instance GEnum f => GEnum (M1 itf) where gFromEnum (M1 x) = gFromEnum x gToEnum n = M1 (gToEnum n) instance (GLeaves x, GEnum x, GEnum y) => GEnum (x :+: y) where gFromEnum (L1 x) = gFromEnum x gFromEnum (R1 y) = gSize (Proxy :: Proxy x) + gFromEnum y gToEnum n = let s = gSize (Proxy :: Proxy x) in if n < s then L1 (gToEnum n) else R1 (gToEnum (n - s))
Finally, you can check this out in GHCi:
ghci> :set -XDeriveAnyClass -XDeriveGeneric ghci> data D = A | B | C | D | E deriving (Show, Generic, Enum, Enum') ghci> toEnum `map` [0 .. 4] :: [D] [A,B,C,D,E] ghci> toEnum' `map` [0 .. 4] :: [D] [A,B,C,D,E] ghci> fromEnum `map` [A .. E] :: [Int] [A,B,C,D,E] ghci> fromEnum' `map` [A .. E] :: [Int] [A,B,C,D,E]
Performance
You may think to yourself: this is super inefficient! As a result, we recount a bunch of sizes over and over again - the worst performance is at least O(n^2) . The trick (hopefully) is that the GHC will be able to optimize / implement the hell out of our specific Enum' instances until there is nothing left of the Generic initial structure.