类型级列表的多态构造函数

时间:2016-03-17 22:09:04

标签: haskell

我有一个类型级别列表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

那么,有没有办法实现这个?

2 个答案:

答案 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,其中Mkdemo1是表示类型级列表的单例构造函数。如果我们不想与单身人士合作,我们可以删除类型索引并使用普通旧数据:

SCons SFoo SNil

现在SCons等于SNil,因此demo1' :: [T] demo1' = fromSing demo1 demo1 - 前缀单例转换回简单表示形式。

与单身人士合作的最大好处是他们完美地反映了这些类型,因此不可能“欺骗”;相反,我们可以在运行时使用[Foo]的任意值,它不符合幻像类型索引。简而言之,在fromSing中,索引只是一个幻像类型,但在S中,索引正好反映在构造函数中。