Вот решение с Generics :
{-# LANGUAGE DeriveGeneric,UndecidableInstances,TypeFamilies,FlexibleInstances #-}
{-# LANGUAGE DataKinds,ConstraintKinds,TypeOperators,TypeApplications #-}
import GHC.Generics
import GHC.TypeLits
import Data.Proxy
class (KnownNat (FiniteEnumSize a)) => BoundedEnum' a where
type FiniteEnumSize a :: Nat
type BoundedEnum a = (Bounded a, Enum a, BoundedEnum' a)
instance BoundedEnum' (V1 a) where
type FiniteEnumSize (V1 a) = 0
instance BoundedEnum' (U1 a) where
type FiniteEnumSize (U1 a) = 1
instance BoundedEnum' c => BoundedEnum' (K1 i c a) where
type FiniteEnumSize (K1 i c a) = FiniteEnumSize c
instance BoundedEnum' (f a) => BoundedEnum' (M1 i t f a) where
type FiniteEnumSize (M1 i t f a) = FiniteEnumSize (f a)
instance ( BoundedEnum' (f a), BoundedEnum' (g a)
, KnownNat (FiniteEnumSize (f a) * FiniteEnumSize (g a)) )
=> BoundedEnum' ((f:*:g) a) where
type FiniteEnumSize ((f:*:g) a) = FiniteEnumSize (f a)
* FiniteEnumSize (g a)
instance ( BoundedEnum' (f a), BoundedEnum' (g a)
, KnownNat (FiniteEnumSize (f a) + FiniteEnumSize (g a)) )
=> BoundedEnum' ((f:+:g) a) where
type FiniteEnumSize ((f:+:g) a) = FiniteEnumSize (f a)
+ FiniteEnumSize (g a)
Тогда вы можете сделать, например,
data Foo = Foo0 | Foo1 | Foo2
deriving (Eq, Enum, Bounded, Show, Generic)
instance BoundedEnum' Foo where
type FiniteEnumSize Foo = FiniteEnumSize (Rep Foo ())
main = print (natVal (Proxy :: Proxy (FiniteEnumSize Foo)))
Результат: 3
.
Это также работает для более сложных ADT, но учтите, что Enum
и Bounded
могут не быть просто производными для таких типов, так что, возможно, лучше просто покончить с этими классами и просто поставить universe
метод в вашем собственном классе.