[] 在 Haskell 的不同上下文中表示两种不同的东西,这让你很难理解其余的实验。
在值级别[] 确实是列表的空构造函数。但是当您询问Property "Colors" [1,2,3,4] 的类型并看到Property [] a 时,您看到的是type 表达式,而不是值表达式。在类型级别没有空列表。1 而[] 是列表类型的类型构造函数。您可以有[Int](整数列表的类型)、[Bool](布尔列表的类型)或[a](a 的多态列表类型); [] 在这些示例中应用于Int、Bool 和a。
如果你愿意,你实际上可以把[Int]写成[] Int,虽然它看起来很奇怪,所以当你想不应用它时,你通常只会在类型级别看到[]。
让我们再看看你的数据声明:
data Property f a = Property String (f a) | Zilch
在左侧,您声明了 type Property 的形状; Property f a 形成一个类型。在右侧,您通过列出可能的值构造函数(Property 和 Zilch)以及其中的“槽”类型来声明该类型中的 值 的形状构造函数(Zilch 没有;String 类型的一个插槽和f a 类型的另一个插槽,Property)。
因此我们可以看出,无论f 和a 是什么,类型表达式f a(f 应用于a)必须形成一个有值的类型。但是f 本身不一定是(事实上它不可能是)一种正常类型的值! Property 值构造函数中没有 f 类型的槽。
一个更清晰的例子是这样的:
*Main> var = Property "Stuff" (Just True)
*Main> :t var
var :: Property Maybe Bool
如果您不知道,Maybe 是一个内置类型,其声明如下所示:
data Maybe a = Just a | Nothing
这个例子很好,因为我们没有在类型级别和值级别使用相同的名称,这样可以避免在您尝试了解事物如何工作时产生混淆。
Just True 是 Maybe Bool 类型的值。在值级别,我们将Just 数据构造函数应用于值True。在类型级别,我们将Maybe 类型构造函数应用于类型Bool。 Maybe Bool 值进入 Property 值构造函数的 f a 槽,这很简单:f 是 Maybe 和 a 是 Bool。
回到你原来的例子:
*Main> var = Property "Colors" [1,2,3,4]
*Main> :t var
var :: Num a => Property [] a
您正在用[1, 2, 3, 4] 填充f a 槽。那是某种数字的列表,所以它是Num t => [t]。所以f a 中的a 是t(需要附带Num 约束),f 是列表类型构造函数 []。这个[] 像Maybe,不像Nothing。
*Main> var = Property "Colors" (1,"Red")
*Main> :t var
var :: Num t => Property ((,) t) [Char]
这里f a 槽被(1, "Red") 填充,它的类型为Num t => (t, [Char])(请记住String 只是[Char] 的另一种写法)。现在要理解这一点,我们必须有点挑剔。现在忘记约束,只关注(t, [Char])。不知何故,我们需要将其解释为应用于其他事物的事物,因此我们可以将其与f a 匹配。事实证明,虽然我们为元组类型(如(a, b))提供了特殊的语法,但它们实际上就像您可以在没有特殊语法的情况下声明的普通ADT。 2 元组类型是一个类型构造函数,我们可以编写 (,) 应用于其他两种类型,在本例中为 t 和 [Char]。而且我们可以使用部分应用的类型构造函数,因此我们可以将(,) 应用于t 视为一个单元,而该单元应用于[Char]。我们可以将这种解释写成 Haskell 类型表达式((,) t) [Char],但我不确定这是否更清楚。但归根结底,我们可以通过将第一个“单元”(,) t 设为 f 并将 [Char] 设为 a 将其与 f a 匹配。然后给我们Property ((,) t) [Char](只是我们还必须放回我们之前忘记的Num t约束)。
最后是这个:
*Main> var = Property "Colors" 20
*Main> :t var
var :: Num (f a) => Property f a
这里我们用20 填充f a 槽,某种数字。我们还没有具体说明这个数字是什么类型,所以 Haskell 愿意相信它可以是 Num 类中的任何类型。但是我们仍然需要该类型具有可以与f a 匹配的“形状”:某些类型构造函数应用于其他类型。它是整个类型表达式f a 需要匹配20 的类型,所以这就是具有Num 约束的东西。但是我们还没有说什么f 或a 可能是什么,20 可以是 any 满足Num 约束的类型,所以它可以是任何@987654418 @ 我们想要它,因此为什么 var 的类型在 f 和 a 中仍然是多态的(只是添加了约束)。
您可能只见过Integer、Int、Double 等数字类型,所以想知道f a 怎么可能是一个数字;所有这些示例都只是单一的基本类型,而不是应用于某物的东西。但是您可以编写自己的 Num 实例,因此 Haskell 永远不会假定给定类型(或类型的形状)不可能是数字,如果您真的尝试使用它,它只会抱怨它找不到Num 实例。所以有时你会得到类似可能错误的东西,但 Haskell 接受(目前)Num 类型的东西是你没想到的。
事实上有内置库中已经有类型,这些类型确实具有复合类型级结构并具有Num 实例。一个例子是Ratio 类型,用于将小数表示为两个整数的比率。您可以使用Ratio Int 或Ratio Integer,例如:
Main*> 4 :: Ratio Int
4 % 1
所以你可以说:
*Main> var = Property "Colors" (20 :: Ratio Integer)
*Main> :t var
var :: Property Ratio Integer
1 实际上可以有,启用DataKinds 扩展以允许镜像几乎任何值的结构的类型,因此您可以拥有类型级列表。但这不是这里发生的事情,它不是一个真正可以使用的功能,直到你很好地掌握了 vanilla Haskell 中类型和值的工作方式,所以我建议你忽略这个脚注,假装它还不存在.