查看Learn You a Haskell中的kind
示例:
data Frank a b = Frank {frankField :: b a} deriving (Show)
LYAH讨论:
我们看到弗兰克有一种* - > (* - > *) - > * 首先 * 代表a,(* - > *)代表b。
当我想到一个例子时,这对我来说很有意义:Frank String Maybe = Frank {frankField :: Maybe String}
。
但是我对如何考虑右侧的kind
感到困惑
... = Frank {frankField :: b a}
。
由于返回类型为frankField :: b a
,kind
的{{1}}如何:
Frank
为什么右侧的* -> (* -> *) -> * -- what does the right-most * represent?
等于kind
?
答案 0 :(得分:10)
嗯,右侧的Frank
是一个值构造函数,值没有种类,它们有类型。类型是指左侧的Frank
,它是一个类型构造函数。将值构造函数重命名为MkFrank
:
data Frank a b = MkFrank {frankField :: b a} deriving (Show)
值构造函数的类型是
MkFrank :: b a -> Frank a b
和类型构造函数的类型是
Frank :: * -> (* -> *) -> *
这表示Frank
采用一种(具体)类型(*
)和一种类型构造函数,它采用一种类型((* -> *)
)并为您提供一种类型(最后{{1} }})。这类似于*
类型的函数:它需要一个"具体的"价值和一个函数(一种获得另一个值的方法),它给你一个"具体的"值,不同之处在于我们处理的是类型而不是值。
作为旁注,类型对类型类的工作方式略有不同(尽管类中没有涉及类)。这是一种名为A -> (B -> C) -> D
的特殊类型,它代表可以在签名中位于Constraint
左侧的内容。
答案 1 :(得分:3)
首先是一些重要的概念点:
类型存在于"限定"一组值,可以用同样的方式处理(在某种意义上说)。类型存在"一个级别"从价值层面,在类型层面。
种类存在于"限定"一组类型 1 ,可以用同样的方式处理(在某种意义上说)。种类存在"一级上升"从类型层面,在种类层面。
在data ... = ...
形式的任何定义中,您要做两件事:
将新的可能实体添加到值级别。定义的右侧给出了#34;形状"您可能的新值(构造函数本身是值,作为函数)。这个使用类型级别的东西来谈论"形状"你的新价值(因为那是什么类型),但声明的东西是价值。
将新实体添加到类型级别。定义的左侧给出了#34;形状"您可能的新类型(类型构造函数本身是类型级别的东西)。
所以希望你能看到种的概念不能适用于整个等式的右边,因为它讨论的是价值层面的事物。它使用某些类型级别的东西来谈论价值观,而那些东西都有种类。左侧作为一个整体 "有一种",因为它引入了一种新类型,但这种类型总是*
所以它不是很有意思。在左侧声明的类型构造函数可以有一个更有趣的类型。
让我们更详细地看一个简单的例子:
data Maybe a = Nothing | Just a
我们已经声明,Nothing
形式和Just a
形式的新内容是值,而Maybe a
形式的新内容是类型(等式"链接"双方a
,以便Just a
属于Maybe a
类型,其中a
是同一个变量)。
Just
本身就是一个构造函数,可以看作是一个存在于值级别的函数,因此有一个类型;它的类型是a -> Maybe a
。 a
有一种,因为它是类型级别的东西,但Nothing
,Just
,整个Just a
和整个右手边{ {1}}可以有意义地说有一种。
在左侧,整个Nothing | Just a
有Maybe a
种类(因为您正在声明具有*
声明的新数据类型,这些声明具有值类型,数据声明的整个左侧始终具有类data
)。 *
本身也作为类型构造函数引入;因为它需要一个参数,它必须有类似Maybe
的类型,我们需要填写空白。
要知道应该填充空白的类型,这是定义中类型变量___ -> *
的类型,我们需要知道如何在RHS上使用a
。我们可以看到它仅用于模式a
。我们知道Just a
是值级别上新内容的形式,因此Just a
必须是a
可能属于的某些值集的类型应用。具有值的类型类型为Just
,因此*
必须属于此类(这是我们避免像a
这样的非敏感内容的方式。这样我们就可以得出结论:Just Maybe
有Maybe
种。
回到你的例子,这非常相似,只是引入的新类型构造函数与引入的新值构造函数同名(都是* -> *
)。这不会导致问题,因为它们存在于不同名称空间 2 的不同级别。但要记住,还有两件不同的事情;右边的Frank
是一个值构造函数,因此讨论它的类型是没有意义的。左侧的Frank
是类型的构造函数,它有一种类型。
但是,在这里,新类型构造函数Frank
有两个参数,因此它必须类似于Frank
。同样,我们需要查看右侧,以了解___ -> ___ -> *
和a
如何用于了解其种类。
这两个变量的唯一用途是值构造函数b
的单个字段的类型;此类型为Frank
。总共b a
是要存储在此字段中的值的类型,因此它必须是b a
种类。这意味着*
类似于b
。
现在我们理论上陷入困境。 ___ -> *
仅用作a
的参数,我们仍然需要填空b
作为参数的类型,因此我们分配给b
的任何类型将工作 3 ,但这会改变a
的类型以及类型构造函数b
。 kind polymorphism的概念适用于此处,但默认情况下,Haskell默认将此类型默认为Frank
。因此*
有a
种类,*
种类b
,因此类型构造函数* -> *
具有Frank
种类。
* -> (* -> *) -> *
(或者当我们变成多态时确实是Frank String Maybe
)Frank a b
应用于两个(类型级别)参数,因此它具有Frank
种类;这是一种价值观所必需的。 *
是Frank (Just "foo")
类型中的值,Frank String Maybe
是类型Frank
中的值,b a -> Frank a b
是frankField
类型中的值};这些都没有(并且所有类型都有类Frank a b -> b a
)。
TLDR: *
的类型为Frank
,b a -> Frank a b
的种类为Frank
,但这两个事实只能是真实的,因为他们正在谈论名为* -> (* -> *) -> *
的不同的实体;因为种类和类型在完全不同的层面上限定事物,所以不可能明智地谈论同一事物的种类和类型。
1 我的第一个谎言; kind类型"类型级别的东西"但不是Haskell中可能存在的所有类型级别的东西都严格来说是类型(取决于你的定义,无论如何)。像Frank
这样的东西是"类型级实体"但它们不是可以与价值观打字关系的东西。
2 它总是可以纯粹从给定名称出现的位置知道它是否应该在类型命名空间或值命名空间中查找,因此编译器永远不会混淆输入名称和值名称。在您使用Haskell之前,这对读者来说并不总是如此!
3 我可以通过使用* -> *
扩展名明确声明这些内容,证明这可以与其他类型一起使用:
KindSignatures
然后在GHCI:
{-# LANGUAGE KindSignatures #-}
data Frank (a :: * -> *) (b :: (* -> *) -> *) = Frank {frankField :: b a} deriving Show
data Strange (a :: * -> *) = Strange (a Int)