采用类似
的数据类型声明data myType = Null | Container TypeA v
据我了解,Haskell将其读为myType
,具有两种不同的风格。其中之一是Null
,Haskell会将其解释为a的某个名称……我想您会称它为该类型的实例吗?还是子类型?因子?水平?无论如何,如果我们将Null
更改为Nubb
,它的行为基本上是相同的-Haskell并不真正了解空值。
另一种口味是Container
,我希望Haskell读这句话是说Container
口味有两个领域,TypeA
和v
。我期望这是因为,在进行此类型定义时,始终将第一个单词读取为口味的名称,并且其后的所有内容都是另一个字段。
我的问题(除了:我有没有遇到任何错误?),Haskell如何知道TypeA
是特定的命名类型而不是无类型的变量?我以为它将v
读为无类型变量是不是很错误,如果是正确的话,是否是因为小写的首字母呢?
通过未键入,我的意思是类型如何在以下函数的类型声明中出现:
func :: a -> a
func a = a
答案 0 :(得分:11)
首先,术语:“风味”被称为“案例”或“构造函数”。您的类型有两种情况-Null
和Container
。
第二,您所谓的“未键入”不是真正的“未键入”。那不是思考的正确方法。声明a
中的func :: a -> a
并不意味着“未类型化”,就像JavaScript或Python中变量“未类型化”的方式一样(尽管那不是真的),而是“由调用此函数的人选择类型”。因此,如果我调用func "abc"
,那么我选择a
为String
,现在编译器知道该调用的结果也必须为String
,因为这就是func
的签名说-“我接受您选择的任何类型,并且我返回相同的类型”。合适的术语是“ 泛型”。
“ untyped”和“ generic”之间的区别在于,“ untyped”是免费的,类型只能在运行时知道,不能保证;泛型类型,尽管尚未确切知道,但它们之间仍然存在某种关系。例如,您的func
说它返回的是相同类型,而不是随机的。再举一个例子:
mkList :: a -> [a]
mkList a = [a]
此函数说:“我选择了某种类型,我将返回相同类型的列表-永远不会返回其他列表”。
最后,您的myType
声明实际上是非法的。在Haskell中,具体类型必须为Capitalized
,而值和类型变量为javaCase
。因此,首先,您必须更改类型的名称才能满足以下要求:
data MyType = Null | Container TypeA v
如果现在尝试编译它,仍然会收到错误消息,提示“类型变量v
未知”。请参阅,Haskell已决定v
必须是类型变量,而不是具体类型,因为它是小写字母。这么简单。
如果要使用类型变量,则必须在某个地方声明它。在函数声明中,类型变量可以“无处不在”,而编译器会认为它们是“已声明”。但是在类型声明中,您必须明确声明类型变量,例如:
data MyType v = Null | Container TypeA v
存在此要求是为了避免在您有多个类型变量或类型变量来自另一个上下文(例如类型类实例)的情况下产生混淆和歧义。
以这种方式声明,每次使用v
时都必须指定一些内容来代替MyType
,例如:
n :: MyType Int
n = Null
mkStringContainer :: TypeA -> String -> MyType String
mkStringContainer ta s = Container ta s
-- Or make the function generic
mkContainer :: TypeA -> a -> MyType a
mkContainer ta a = Container ta a
答案 1 :(得分:9)
Haskell在变量和构造函数之间使用了至关重要的区别。变量以小写字母开头;构造函数以大写字母 1 开头。
因此data myType = Null | Container TypeA v
实际上是不正确的; data
关键字后的第一个符号是您要引入的新类型构造函数的名称,因此它必须以大写字母开头。
假设您已将其固定为data MyType = Null | Container TypeA v
,则由|
分隔的每个替代项都必须包含一个数据构造函数名称(此处选择了Null
和{ {1}}),然后是该构造函数每个字段的类型表达式。
Container
构造函数没有字段。 Null
构造函数有两个字段:
Container
,以大写字母开头,因此它必须是类型构造函数;因此该字段属于该具体类型。TypeA
,以小写字母开头,因此是类型变量。通常,此变量将被定义为正在定义的v
类型上的类型参数,例如MyType
。您通常不能使用自由变量,因此这是您原始示例中的另一个错误。 2 您的数据声明显示了构造函数和变量之间的区别在类型级别上的重要性。变量和构造函数之间的区别也存在于值级别。这是编译器如何(在您编写模式匹配项时)告诉您哪些术语是应根据其检查数据的模式,以及哪些术语是应绑定到数据所包含内容的变量。例如:
data MyType v = Null | Container TypeA v
如果Haskell没有第一个字母规则,则该函数的第一个子句将有两种可能的解释:
lookAtMaybe :: Show a => Maybe a -> String
lookAtMaybe Nothing = "Nothing to see here"
lookAtMaybe (Just x) = "I found: " ++ show x
可以是对外部定义的Nothing
构造函数的引用,说我希望当参数与该构造函数匹配时应用此函数规则。这是第一字母规则要求的解释。Nothing
可以是(未使用的)变量的定义,代表函数的自变量。这相当于Nothing
这两种解释都是有效的Haskell代码,它们产生不同的行为(尝试将大写字母lookAtMaybe x = "Nothing to see here"
更改为小写字母N
,并查看该函数的作用)。因此,Haskell需要一个规则在它们之间进行选择。设计人员选择了第一字母规则,以将构造函数与变量(对编译器和人类读者来说都是简单的)简单地消除歧义,而无需任何其他语法噪音。
1 有关首字母大小写的规则适用于字母数字名称,该字母数字名称只能由字母,数字和下划线组成。 Haskell还具有符号名称,该名称仅由符号字符组成,例如n
,+
,*
等。为此,规则是名称以:
字符开头是构造函数,而以另一个字符开头的名称是变量。这就是列表构造函数:
与函数名称:
的区别。
2 启用+
扩展名后可以编写ExistentialQuantification
,以便构造函数具有一个具有变量类型的字段,并且变量执行 not 作为整体类型的参数出现。我不会在这里解释它是如何工作的。它通常被认为是高级功能,并且不是标准Haskell代码的一部分(这就是为什么需要扩展)