Haskell 种类列表到底是如何工作的?

Haskell 种类列表到底是如何工作的?,haskell,data-kinds,Haskell,Data Kinds,我最近一直在读《乙烯基》杂志,它使用了奇怪的“种类列表”类型。在读了一些关于种类和乙烯基的文章后,我对它们有了一些直观的理解,并且我能够将它们结合起来 {-# LANGUAGE DataKinds, TypeOperators, FlexibleInstances, FlexibleContexts, KindSignatures, GADTs #-} module

我最近一直在读《乙烯基》杂志,它使用了奇怪的“种类列表”类型。在读了一些关于种类和乙烯基的文章后,我对它们有了一些直观的理解,并且我能够将它们结合起来

{-# LANGUAGE DataKinds,
             TypeOperators,
             FlexibleInstances,
             FlexibleContexts,
             KindSignatures,
             GADTs #-}
module Main where

-- from the data kinds page, with HCons replaced with :+:
data HList :: [*] -> * where
  HNil :: HList '[]
  (:+:) :: a -> HList t -> HList (a ': t)

infixr 8 :+:


instance Show (HList '[]) where
  show _ = "[]"
instance (Show a, Show (HList t)) => Show (HList (a ': t)) where
  show (x :+: xs) = show x ++ " : " ++  show xs

class ISum a where
  isum :: Integral t => a -> t

instance ISum (HList '[]) where
  isum _ = 0


instance (Integral a, ISum (HList t)) => ISum (HList (a ': t)) where
  isum (x :+: xs) = fromIntegral x + isum xs

-- explicit type signatures just to check if I got them right
alist :: HList '[Integer]
alist = (3::Integer) :+: HNil

blist :: HList '[Integer,Int]
blist =  (3::Integer) :+: (3::Int) :+: HNil

main :: IO ()
main = do
  print alist
  print (isum alist :: Int)
  print blist
  print (isum blist :: Integer)
:i HList
产量

data HList $a where
  HNil :: HList ('[] *)
  (:+:) :: a -> (HList t) -> HList ((':) * a t)
    -- Defined at /tmp/test.hs:10:6
instance Show (HList ('[] *)) -- Defined at /tmp/test.hs:17:10
instance (Show a, Show (HList t)) => Show (HList ((':) * a t))
  -- Defined at /tmp/test.hs:19:10
instance ISum (HList ('[] *)) -- Defined at /tmp/test.hs:25:10
instance (Integral a, ISum (HList t)) => ISum (HList ((':) * a t))
  -- Defined at /tmp/test.hs:29:10
*Main> :i HList
data HList $a where
  HNil :: HList ('[] *)
  (:+:) :: a -> (HList t) -> HList ((':) * a t)
    -- Defined at /tmp/test.hs:10:6
instance Show (HList ('[] *)) -- Defined at /tmp/test.hs:17:10
instance (Show a, Show (HList t)) => Show (HList ((':) * a t))
  -- Defined at /tmp/test.hs:19:10
instance ISum (HList ('[] *)) -- Defined at /tmp/test.hs:25:10
instance (Integral a, ISum (HList t)) => ISum (HList ((':) * a t))
  -- Defined at /tmp/test.hs:29:10

从中我推断,
'[]
'[]*
的糖,
x':y
(':)*xy
。那在那里做什么?它是列表元素的类型吗?还有,这样的清单到底是什么?它是语言中固有的东西吗?

*
是。。。不幸。这是GHC用于多种类数据类型的漂亮打印机的结果。它会导致语法上无效的东西,但它确实传递了一些有用的信息

当GHC pretty打印具有多态类型的类型时,它会在类型构造函数之后打印每个多态类型变量的类型。整齐所以如果你有一个声明,比如:

data Foo (x :: k) y (z :: k2) = Foo y
GHC会将
Foo
(数据构造函数)的类型打印为
y->fookk1xyz
。如果您使用了某种类型的变量,比如

foo :: a -> Int -> Foo a Int 5 -- Data Kind promoted Nat
foo“hello”0的类型将被打印为
foo*Nat String Int 5
。是啊,太可怕了。但如果你知道发生了什么,至少你能读出来

“[]
东西是
数据种类
扩展的一部分。它允许将类型升级为种类,并且该类型的构造函数将成为类型构造函数。这些升级的类型没有有效值,甚至没有未定义的
,因为它们的类型与
*
不兼容,而
*
是所有可以具有值的类型的类型。因此,它们只能出现在没有该值类型的地方。有关详细信息,请参阅

编辑:

您的评论提出了一些关于ghci工作方式的观点

-- foo.hs
{-# LANGUAGE DataKinds, PolyKinds #-}

data Foo (x :: k) y (z :: k1) = Foo y
在ghci中加载文件时,它不会以交互方式激活文件中使用的扩展名

GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :l foo
[1 of 1] Compiling Main             ( foo.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t Foo
Foo :: y -> Foo * * x y z
*Main> :set -XPolyKinds
*Main> :t Foo
Foo :: y -> Foo k k1 x y z
所以,是的。必须在ghci中启用
polytypes
扩展,才能将其默认为类型中的多态种类。我也尝试在文件中定义我的
foo
函数,但它确实使这个版本的ghc崩溃。哎呀。我想现在已经解决了,但我想检查一下ghc跟踪系统会很好。在任何情况下,我都可以以交互方式定义它,而且效果很好

*Main> :set -XDataKinds
*Main> let foo :: a -> Int -> Foo a Int 5 ; foo = undefined
*Main> :t foo "hello" 0
foo "hello" 0 :: Foo * GHC.TypeLits.Nat [Char] Int 5
*Main> :m + GHC.TypeLits
*Main GHC.TypeLits> :t foo "hello" 0
foo "hello" 0 :: Foo * Nat [Char] Int 5
好吧,我忘了需要导入才能显示
Nat
unqualified。因为我只是演示打印,所以我不关心实现,所以
未定义的
就足够了


但我保证,我说的一切都会成功。我只是遗漏了一些需要扩展的细节,特别是
polytypes
datatypes
。我认为既然您在代码中使用了这些,您就理解了它们。以下是关于
多种类
扩展的文档:

这是由于一些关于打印的不幸实现。种类可以被认为是“种类的种类”。注意以下几点:

>:t []
[] :: [a]
>:k '[]
'[] :: [*]
就像
[a]
表示“对于所有类型的a,
[a]
”,
[*]
表示“对于所有类型的
*
[*]
”。但是,您可以使用种类进行的推理量要比使用类型进行的推理量小得多。例如,
a->a
表示两个
a
都是相同的类型,但
*->*
表示两个
*
都可以是任何类型(可以认为
*->*
a->b
提升到种类级别的类型)。但是无法将类型
a->a
提升到种类级别。这意味着
[a]
[*]
并不十分相似
[*]
更接近于
[对于所有a.a]
。更简洁但不太准确的是,你可以说没有办法区分“多态”种类,因为没有“种类变量”这样的东西。(旁注:
-XPolyKinds
启用了文档中所称的“多态类型”,但它仍然不能提供真正的多态性)

因此,当您编写
HList::[*]->*
(这实际上意味着
HList(k::[*]):*
)时,您指示第一个类型参数的种类应该是
[*]
,而种类
[*]
可以采用的“值”是
'[]
*':'[]
':'.*:[]
,等等

现在问题来了。当打印种类受到限制的对象时,例如
HNil
的第一个类型参数,它将尝试包含所有种类信息。不管出于什么原因,我都不会写

HNil :: HList ('[] :: '[*])
^ data         ^ type   ^ kind 
这实际上表明
*
指的是那种
'[]
,它以你所看到的非常混乱的格式打印东西。有必要拥有这些信息,因为列表中“存储”的内容不一定是开放类型(这是
*
的名称)。你可以有这样的东西:

data Letter = A | B -- | C .. etc
data LetterProxy p 

data HList (k :: [Letter])  where
  HNil  :: HList '[]
  (:+:) :: LetterProxy (a :: Letter) -> HList t -> HList (a ': t)
这是非常愚蠢的,但仍然有效


我认为这种不完美的特征是由两个原因造成的。首先,如果以我所述的格式打印内容,对于某些数据类型或类,
:I
的结果将非常长,并且非常不可读。其次,种类是非常新的(仅从7.4开始),所以没有多少时间花在决定如何打印种类上,因为还没有人确定种类应该/将如何工作

非常清楚!一段时间以来,我一直在努力彻底理解这个语法。我不太明白这个答案的大部分。您的示例只适用于-XPolyKinds-另一个我不太了解的扩展-并且
:t Foo
对我来说是
y->Foo**x y z
,而不是您得到的。如果我尝试引入一个函数foo-你没有给出定义