类型类
在计算机科学中,类型类(type class),是支持特设多态的类型系统构造。这是通过向参数多态类型的类型变量增加约束完成的。这种约束典型的涉及到一个类型类T
和一个类型变量a
,并意味着a
所能实例化的类型,其成员必须支持关联于T
的重载运算。
类型类首先在Haskell中实现,当时Philip Wadler和Stephen Blott提出它,作为对Standard ML的eqtype
的扩展[1][2],并且最初构想为以本原方式实现重载算术及等式算符的一种途径[3][4]。
对比于Standard ML的“eqtypes”,在Haskell中通过使用类型类重载等式算符,不要求编译器前端或底层类型系统的广泛修改[5]。
自从它们创立之后,已经发现了类型类的很多其他应用。
概述
定义类型类需要通过规定一组函数或约束名字,它们分别具有同在的类型,它们对属于这个类的所有类型都必须存在。在Haskell中,类型可以被参数化,意图包含允许等式的类型一个类型类Eq
,可以如下这样声明:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x /= y = not (x == y)
x == y = not (x /= y)
这里的a
是类型类Eq
的一个实例,并且a
定义的函数签名,针对了2个函数(等式和不等式函数),它们每个都接受2个类型a
的实际参数并返回一个布尔值。这个声明可以读作:“类型a
属于类型类Eq
,如果在其上定义了有适当类型的,叫做(==)
、(/=)
的的函数。”
编程者可以使任何类型t
成为给定类型类C
的成员,通过使用“实例声明” 为特定类型t
实现所有的C
方法。如果编程者定义一个新的记录数据类型Foo
,可以接着使这个新类型成为Eq
的实例,通过以任何他们认为合适的方式,提供在类型Foo
的值之上的等式函数和不等式函数,例如:
data Foo = Foo {x :: Integer, str :: String}
instance Eq Foo where
(Foo x1 str1) == (Foo x2 str2) = (x1 == x2) && (str1 == str2)
编程者可以接着如下这样定义函数elem
,它确定一个元素是否在一个列表之中:
elem :: Eq a => a -> [a] -> Bool
elem y [] = False
elem y (x:xs) = (x == y) || elem y xs
函数elem
的类型a -> [a] -> Bool
具有上下文Eq a
,将参数多态函数的类型变量a
可包括的类型,约束为属于Eq
类型类的那些类型。Haskell中的 =>
可表示“类型类继承”或“类型约束”。函数elem
可应用于属于Eg
类型类的任何类型的元素和这个类型的列表二者之上。
注意类型类不同于面向对象编程语言中的类。特别是,Eq
不是一个类型:没有类型Eq
的值这种东西。类型变量a
有种类(kind),在最新的GHC发行中叫做Type
[6]。意味着Eq
的种类是:
Eq :: Type -> Constraint
高种类多态
类型类不但接受种类Type
的类型变量,它可以接受任何种类的类型变量。这些有更高种类的类型类有时叫做构造子类,这里的构造子指称的是类型构造子比如Maybe
,而非数据构造子比如Just
。例如单子类Monad
:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
m
应用到一个类型变量上的事实,指出了它有着种类Type -> Type
,就是说它接受一个类型并返回一个类型,因而Monad
的种类是:
Monad :: (Type -> Type) -> Constraint
多参数类型类
类型类允许多个类型参数,因此类型类可以被看作在类型上的关系[7]。例如,在GHC标准库中,类IArray
表达一个通用不可变数组接口。在这个类中,类型约束IArray a e
意味着a
是一个数组类型,它包含类型e
的元素。例如,在多态上的这种限制被用来实现开箱数组类型。
函数依赖
在Haskell中,类型类已经被精制为允许编程者声明在类型参数之间的函数依赖,这个概念受到关系数据库理论中的函数依赖的启发[8][9]。就是说,编程者可以断言对类型参数的某个子集的给定指派,唯一性的确定余下的类型参数。例如,承载一个类型s
的状态参数的,一个一般性的单子m
,满足类型类约束Monad.State s m
。在这个约束中,有函数依赖m -> s
。这意味着对于类型类Monad.State
的一个给定单子m
,从m
能访问到的状态类型是被唯一性确定的。这会辅助编译器进行类型推论,还能辅助编程者进行类型导向编程。
Simon Peyton-Jones因其复杂性而反对在Haskell中介入函数依赖[10]。
运算符重载的其他方式
在Standard ML中,“等式类型”的机制粗略的对应于Haskell的内建类型类Eq
,但是所有等式算符都自动的由编译器导出。编程者的过程控制局限于,指定在一个结构中哪些类型成员是等式类型,和在多态类型中哪些类型变量涉及等式类型。
SML和OCaml的模块和函子可以扮演类似于Haskell的类型类的角色,原则区别在于类型推论的角色,它使得类型类适合于特设多态[11]。OCaml的面向对象子集是某种程度上与类型类有可比性的另一种方式。
有关概念
用于重载数据的一个类似概念(实现于GHC中)是类型家族[12]。
在Clean中的类型类与Haskell相似,但是有稍微不同的语法。
Rust支持trait,这是具有一致性的有限形式的类型类[13]。
Mercury有类型类,却不完全同于Haskell。
在Scala中,类型类是编程惯例,可以用现存语言特征比如隐式参数来实现,本身不是独立的语言特征。由于它们在Scala中的这种实现方式,在有歧义的情况下,有可能显式的指定哪种类型类实例用作在代码中特定位置上的类型。但是,这不必然有益处,因为有歧义的类型类实例是易于出错的。
证明辅助Coq在新近版本也支持类型类。不像在平常编程语言中那样,在Coq中,在类型类定义内陈述的任何类型类定律(比如单子定律),在使用它们之前,必须对每个类型类实例进行数学证明。
参见
- 多态 (计算机科学)
- 运算符重载 - 类型类的应用之一
- 单子 (函数式编程),函子 (函数式编程) - 是类型类的例子
- 概念 (C++)(自从C++20)
引用
- Morris, John. (PDF). 2013 [2021-02-08]. (原始内容存档 (PDF)于2020-11-04).
- Wadler, Philip. . October 1988.
- Kaes, Stefan. . . March 1988. doi:10.1007/3-540-19027-9_9.
- Wadler, Philip; Stephen Blott. . . January 1989 [2021-02-08]. (原始内容存档于2016-03-11).
- Appel, Andrew; David MacQueen. . . June 1991 [2021-02-08]. (原始内容存档于2008-05-09).
-
Type
(页面存档备份,存于) fromData.Kind
appeared in version 8 of the Glasgow Haskell Compiler - Haskell' page MultiParamTypeClasses (页面存档备份,存于).
- Mark Jones. Type Classes with Functional Dependencies (页面存档备份,存于). From Proc. 9th European Symposium on Programming. March, 2000.
- Haskell' page FunctionalDependencies (页面存档备份,存于).
- . [2021-02-10]. (原始内容存档于2014-12-25).
- Dreyer, Derek; Robert Harper; Manuel M.T. Chakravarty. . April 2006 [2021-03-01]. (原始内容存档于2007-05-19).
|book-title=
被忽略 (帮助) - . [2021-02-10]. (原始内容存档于2014-10-28).
- . [2021-02-10]. (原始内容存档于2020-11-12).
外部链接
維基教科書中的相關電子:类和类型 |
- A Gentle Introduction to Haskell, Version 98, chapter 5. Type Classes and Overloading (页面存档备份,存于). June 2000.
- Implementing, and Understanding Type Classes (页面存档备份,存于). 2014-11-13.