Получение размера ограниченного перечисления как Nat - PullRequest
1 голос
/ 11 февраля 2020

Для библиотеки, которую я пишу, я хотел бы иметь возможность получить размер любого типа с ограничениями Bounded и Enum как уровень типа 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

Единственное другое «решение» I Можно подумать о том, чтобы вручную определить экземпляры BoundedEnum для всех типов, которые имеют экземпляры Enum и Bounded, но это привело бы ко многим потерянным экземплярам для пользователей библиотеки (поскольку я не смог бы определить все необходимые экземпляры без импорта всей вселенной).

Ответы [ 2 ]

1 голос
/ 11 февраля 2020

Вот решение с 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 метод в вашем собственном классе.

...