检索有界枚举的大小作为 Nat

Retrieving the size of a Bounded Enum as a Nat

对于我正在编写的库,我希望能够检索具有 BoundedEnum 约束的任何类型的大小,作为类型级别 Nat .目的是定义类型类实例如:

instance ( Enum a, Bounded a, n ~ BoundedEnumSize a ) => Action ( CyclicGroup n ) ( CyclicEnum a ) where
  ...

是否有可能使用模板 Haskell 来实现此目的,例如

class    ( Enum a, Bounded a ) => BoundedEnum a where
  type FiniteEnumSize a :: Nat
instance ( Enum a, Bounded a ) => BoundedEnum a where
  type BoundedEnumSize a = ... Template Haskell ... 1 + fromEnum maxBound - fromEnum minBound

我能想到的唯一其他 "solution" 是为同时具有 EnumBounded 实例的所有类型手动定义 BoundedEnum 实例,但这会导致库的用户有许多孤立实例(因为如果不导入整个宇宙,我将无法定义所有必要的实例)。

这是 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——但请注意 EnumBounded 可以 而不是 可以简单地派生出这些类型,所以也许更好完全取消那些 classes 并简单地在您自己的 class.

中放置一个 universe 方法

finitary 库正是我要找的,因为它允许在类型级别访问任何有限类型的基数,这适用于包含字段的类型。