我有一个类型级别列表L,我正在尝试编写多态mkL,可以按如下方式使用:
{-# LANGUAGE TypeOperators, PolyKinds, DataKinds, TypeFamilies,
UndecidableInstances, AllowAmbiguousTypes, TypeSynonymInstances,
FlexibleInstances, ScopedTypeVariables #-}
data T = Foo | Bar
deriving (Show, Eq)
data L (ts :: [T]) = L [T]
deriving (Show, Eq)
demo1 :: L '[Foo]
demo1 = mkL
demo2 :: L '[Foo, Bar]
demo2 = mkL
我让demo1工作得很轻松,但递归案例因某种原因打败了我。
class MkL p where
mkL :: p
instance MkL (L '[]) where
mkL = L []
instance MkL (L '[Foo]) where
mkL = L [Foo]
instance MkL (L '[Bar]) where
mkL = L [Bar]
instance MkL (L (l1 ': l2 ': ls)) where
mkL =
let (L [l1]) = undefined -- FIXME
(L [l2]) = undefined
(L rest) = undefined
in L (l1 : l2 : rest)
如果我用mkL替换FIXME,我得到:
No instance for (MkL (L t0)) arising from a use of ‘mkL’
The type variable ‘t0’ is ambiguous
Note: there are several potential instances:
instance MkL (L (l1 : l2 : ls)) -- Defined at Target.hs:19:10
instance MkL (L '['Bar]) -- Defined at Target.hs:17:10
instance MkL (L '['Foo]) -- Defined at Target.hs:15:10
...plus one other
In the expression: mkL
In a pattern binding: (L [l1]) = mkL
那么,有没有办法实现这个?
答案 0 :(得分:3)
您必须根据列表的递归结构在列表上编写递归函数。列表可以是[]
或_ : _
:
instance MkL (L '[]) where
mkL = L []
instance MkL (L xs) => MkL (L ('Foo ': xs)) where
mkL = let L xs = mkL :: L xs in L (Foo : xs)
instance MkL (L xs) => MkL (L ('Bar ': xs)) where
mkL = let L xs = mkL :: L xs in L (Bar : xs)
但应该注意的是,您的L
类型可能不是您想要的。就编译器而言,值级[T]
和类型级[T]
之间没有关系。这完全有效:
instance MkL (L xs) where
mkL = L []
要存储类型级别列表的值级别表示,您必须执行类似
的操作data family SingT (x :: k)
data instance SingT (x :: [k]) where
Nil :: SingT '[]
Cons :: SingT x -> SingT xs -> SingT (x ': xs)
data instance SingT (x :: T) where
SFoo :: SingT 'Foo
SBar :: SingT 'Bar
class Sing a where
sing :: SingT a
instance Sing 'Foo where sing = SFoo
instance Sing 'Bar where sing = SBar
instance (Sing x, Sing xs) => Sing (x ': xs) where sing = Cons sing sing
instance Sing '[] where sing = Nil
然后你有sing :: SingT '[ 'Foo, 'Bar ]
等,这种类型只有Cons SFoo (Cons SBar Nil)
居住。有一些包,例如singletons,它们部分地自动化了定义这种SingT
类型的过程。
答案 1 :(得分:1)
您必须考虑三种情况:空列表,头部带有Foo
的列表,以及带有Bar
的列表。转换为实例:
instance MkL (L '[]) where
mkL = L []
instance MkL (L ts) => MkL (L (Foo ': ts)) where
mkL = case mkL :: L ts of L ts -> L (Foo : ts)
instance MkL (L ts) => MkL (L (Bar ': ts)) where
mkL = case mkL :: L ts of L ts -> L (Bar : ts)
这里没有必要考虑某些n + 2
的{{1}}长度列表,因为我假设我们希望所有n
都有实例,空{{1}构造函数覆盖了它们。
另一方面,有一个existing library涵盖了这个用例,它更安全,通常更强大。它允许我们生成(通过模板Haskell,但也可以手工编写)类型级数据的值级代表(称为“单例”)或值级函数的类型级代表,并为我们提供了大量工具与他们合作。
例如:
ts :: [T]
这定义了(除其他事项外)(:)
和{-# LANGUAGE TemplateHaskell #-} -- on the top of the others
import Data.Singletons.TH
$(singletons [d| data T = Foo | Bar |])
构造函数,其中SFoo :: Sing Foo
是一个数据系列,SBar :: Sing Bar
恰好包含某些{的值级代表{ {1}}类型。对于每个Sing
,只有一个值(这就是为什么它是单例),因此可以从其单例中明确地确定一个类型。
Sing x
x
与您的Sing x
类相似,但它适用于多种不同类型。
此处,sFoo :: Sing Foo
sFoo = SFoo
sBar :: Sing Bar
sBar = SBar
demo1 :: Sing '[Foo]
demo1 = sing
demo2 :: Sing '[Foo, Bar]
demo2 = sing
等于sing
,其中Mk
和demo1
是表示类型级列表的单例构造函数。如果我们不想与单身人士合作,我们可以删除类型索引并使用普通旧数据:
SCons SFoo SNil
现在SCons
等于SNil
,因此demo1' :: [T]
demo1' = fromSing demo1
将demo1
- 前缀单例转换回简单表示形式。
与单身人士合作的最大好处是他们完美地反映了这些类型,因此不可能“欺骗”;相反,我们可以在运行时使用[Foo]
的任意值,它不符合幻像类型索引。简而言之,在fromSing
中,索引只是一个幻像类型,但在S
中,索引正好反映在构造函数中。