我有一个类型类Cyclic
我希望能够提供泛型实例。使用GHC.Generics推导默认实例
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
考虑之类无参构造函数,
data T3 = A | B | C deriving (Generic, Show)
我要生成实例相当于这一个:
instance Cyclic T3 where
gen = A
rot A = B
rot B = C
rot C = A
ord _ = 3
我试图找出所需Generic
机械像这样
{-# LANGUAGE DefaultSignatures, FlexibleContexts, ScopedTypeVariables, TypeOperators #-}
import GHC.Generics
class GCyclic f where
ggen :: f a
grot :: f a -> f a
gord :: f a -> Int
instance GCyclic U1 where
ggen = U1
grot _ = U1
gord _ = 1
instance Cyclic c => GCyclic (K1 i c) where
ggen = K1 gen
grot (K1 a) = K1 (rot a)
gord (K1 a) = ord a
instance GCyclic f => GCyclic (M1 i c f) where
ggen = M1 ggen
grot (M1 a) = M1 (grot a)
gord (M1 a) = gord a
instance (GCyclic f, GCyclic g) => GCyclic (f :*: g) where
ggen = ggen :*: ggen
grot (a :*: b) = grot a :*: grot b
gord (a :*: b) = gord a `lcm` gord b
instance (GCyclic f, GCyclic g) => GCyclic (f :+: g) where
ggen = L1 ggen
-- grot is incorrect
grot (L1 a) = L1 (grot a)
grot (R1 b) = R1 (grot b)
gord _ = gord (undefined :: f a)
+ gord (undefined :: g b)
现在
我可以使用GCyclic
Cyclic
为提供默认的实现:
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
default gen :: (Generic g, GCyclic (Rep g)) => g
gen = to ggen
default rot :: (Generic g, GCyclic (Rep g)) => g -> g
rot = to . grot . from
default ord :: (Generic g, GCyclic (Rep g)) => g -> Int
ord = gord . from
但我GCyclic
情况下是不正确的。从上面
λ. map rot [A, B, C] -- == [B, C, A]
[A, B, C]
很清楚为什么rot
相当于id
这里使用T3
。 grot
向下递减结构T3
,直到它遇到基础案例grot U1 = U1
。
在#haskell
上建议使用M1
的构造函数信息,所以grot
可以选择下一个构造函数进行递归,但我不确定如何执行此操作。
是否可以使用GHC.Generics
或某种其他形式的Scrap Your Boilerplate生成所需的Cyclic
实例?
编辑:我可以编写使用Bounded
和Enum
class Cyclic g where
gen :: g
rot :: g -> g
ord :: g -> Int
default gen :: Bounded g => g
gen = minBound
default rot :: (Bounded g, Enum g, Eq g) => g -> g
rot g | g == maxBound = minBound
| otherwise = succ g
default ord :: (Bounded g, Enum g) => g -> Int
ord g = 1 + fromEnum (maxBound `asTypeOf` g)
,但(这是),这是不能令人满意的,因为它要求所有的Bounded
,Enum
和Eq
Cyclic
。此外,在某些情况下,Enum
不能由GHC自动派生,而更稳健的Generic
可以。
也许这完全不符合你的问题,但你可以为那些只使用Enum和Bounded的函数提供默认值。然后你所要做的就是声明实例,不需要具体实现。我现在正在打电话,但我可以稍后再提供一个例子。尽管如此,我的感觉是你的实际使用情况有点复杂。 – bheklilr
“Bounded”和“Eq”中唯一需要的是能够告诉你什么时候开始从最后一个项目开始迭代其他'gen',这就是我的答案所增加的。请注意,在'GCylic'中加入'glast'并不要求你为'Cyclic'添加一个相应的函数,除非你打算为'K1'派生实例(你完全应该这样做,因为它很棒;派生实例' T3]'可能会让你大吃一惊,令我感到惊讶)。 – Cirdec
如果你要开始传递'undefined'值作为类型的代理,实现'Cyclic'的所有东西都需要接受'undefined'值,因为有些实现可能会把undefined传递给其他类型的值。你可以通过改变标签包(http://hackage.haskell.org/package/tagged)中的'data Proxy a = Proxy'来避免这种情况,而是通过'(Proxy :: ...)'传递。你会更改为'ord :: Proxy a - > Int'。 – Cirdec