类型类
在计算机科学中,类型类是支持临时多态性的类型系统结构。这是通过在参数多态类型中添加约束的变量来实现的。这样的约束通常涉及T
类和类型变量a
,这意味着a
只能将其实例化为其成员支持与T
相关的过载操作的类型。
菲利普·沃德勒(Philip Wadler)和斯蒂芬·布洛特(Stephen Blott)首先提出的作为标准ML中“ eqtypes”的扩展,最初被认为是实现超载算术和平等运营商的一种方式。与标准ML的“ eqtypes”相反,通过在Haskell中使用类型类的类型类超载不需要对编译器前端或基础类型系统进行大量修改。
概述
类型类是通过指定一组函数或常数名称以及它们各自的类型来定义的,这些函数或常数类型必须存在于属于类的每个类型。在Haskell中,可以参数化类型;类型类Eq
旨在包含承认平等的类型,将以以下方式声明:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
如果a
是类型类Eq
的一个实例,并且a
定义了2个函数的函数签名(平等和不平等函数),则每个功能都采用2个a
的参数并返回布尔值。
类型变量a
有友善 ((
在最新的GHC版本中也称为
Type
),这意味着Eq
的种类
Eq :: Type -> Constraint
声明可以读取为“属于类型类式”的“类型a
Eq
,如果有命名(==)
和(/=)
的适当类型的函数,则在其上定义了”。然后,程序员可以通过以下方式定义函数elem
(该函数是否确定元素是否在列表中):
elem :: Eq a => a -> [a] -> Bool
elem y [] = False
elem y (x:xs) = (x == y) || elem y xs
该函数elem
具有带有上下文Eq a
类型a -> [a] -> Bool
,该函数限制了a
可以范围内的类型到属于Eq
类型类的a
(注意:Haskell =>
可以称为“类约束”。)
程序员可以通过使用定义特定类型t
的所有C
的实现的实例声明来使任何类型T类C
的成员成为C类t
的成员。例如,如果程序员定义了一个新的数据类型t
,则可以通过以任何合适的方式提供与t
型值相比,将此新类型作为Eq
的实例。一旦完成此操作,他们可能会在[t]
上使用函数elem
,即类型t
元素的列表。
请注意,类型类别与面向对象的编程语言中的类不同。特别是, Eq
不是一种类型:没有类型Eq
的值之类的东西。
类型类与参数多态性密切相关。例如,请注意,上面指定的elem
类型将是参数性多态性a -> [a] -> Bool
类型不是用于类型类约束“ Eq a =>
”。
更高的多态性
类型类不需要采用类型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
多参数类型类
类型类允许多个类型参数,因此类型类可以视为类型上的关系。例如,在GHC标准库中, IArray
类表达一个一般不可变的数组接口。在此类中,类型类约束IArray ae
表示a
是包含e
类型元素的数组类型。 (例如,对多态性的这种限制用于实现未框的数组类型。)
像多方法一样,多参数类型类都支持调用方法的不同实现方法,具体取决于多个参数的类型,实际上是返回类型。多参数类型类不需要搜索运行时呼叫的每个呼叫的方法;而是与单参数类型类一样,首先将呼叫的方法汇编并存储在类型类实例的字典中。
使用多参数类型类的Haskell代码不可移植,因为此功能不属于Haskell 98标准。受欢迎的Haskell实现GHC和HUGS支持多参数类型类。
功能依赖性
在Haskell中,已经对类型类进行了完善,以允许程序员在类型参数之间声明功能依赖性,这是从关系数据库理论启发的概念。也就是说,程序员可以断言类型参数的某个子集的给定分配唯一地确定了其余类型参数。例如,带有类型s
的状态参数的一般单子m
满足类型类约束Monad.State sm
。在此约束中,有一个功能依赖性m -> s
。这意味着,对于属于类型的单元的给定Monad.State
m
,可以从m
中访问的状态类型是唯一确定的。这有助于编译器的类型推理,并帮助程序员进行类型定向的编程。
西蒙·佩顿·琼斯(Simon Peyton-Jones)反对以复杂性为基础引入Haskell中的功能依赖性。
类型类和隐式参数
类型类和隐式参数本质上非常相似,尽管不完全相同。具有类型类约束的多态性功能,例如:
sum :: Num a => [a] -> a
可以直观地将其视为隐式接受Num
实例的函数:
sum_ :: Num_ a -> [a] -> a
实例Num_ a
本质上是包含Num a
的实例定义的记录。 (实际上,这是格拉斯哥Haskell编译器在引擎盖下实现类型类的方式。)
但是,存在至关重要的差异:隐式参数更灵活- 您可以传递Num Int
的不同实例。相比之下,类型类强制执行所谓的连贯性属性,该属性要求任何给定类型的实例只有一个独特的实例选择。连贯性属性使类型类有些抗性,这就是为什么强烈劝阻孤儿实例(在模块中定义的实例,在模块中定义的实例)强烈灰心。另一方面,连贯性为语言增加了一个额外的安全性,提供程序员保证同一代码的两个差异部分将共享相同的实例。
例如,(类型Set a
)的有序集需要在元素上( a
类型)的总订购才能功能。约束Ord a
可以证明这是在元素上定义比较运算符的。但是,可能有许多施加总订单的方法。由于集合算法一旦构造了一旦构建了一个集合,因此订购的变化通常不宽容,因此将Ord a
的不兼容实例传递给在集合上运行的函数可能会导致结果不正确(或崩溃)。因此,在这种特定情况下执行Ord a
的连贯性至关重要。
Scala类型类中的实例(或“字典”)只是语言中的普通值,而不是完全独立的实体。尽管默认情况下,这些实例是通过在范围中找到适当的实例作为明确指定的隐式形式参数的隐式实际参数提供的,但它们是普通值的事实意味着可以明确地提供它们,以解决模棱两可。结果,Scala类型类不满足相干性能,并且实际上是隐式参数的句法糖。
这是从猫文档中获取的一个例子:
// A type class to provide textual representation
trait Show[A] {
def show(f: A): String
}
// A polymorphic function that works only when there is an implicit
// instance of Show[A] available
def log[A](a: A)(implicit s: Show[A]) = println(s.show(a))
// An instance for String
implicit val stringShow = new Show[String] {
def show(s: String) = s
}
// The parameter stringShow was inserted by the compiler.
scala> log("a string")
a string
COQ (版本8.2开始)还通过推断适当的实例来支持类型类。 AGDA 2的最新版本也提供了类似的功能,称为“实例参数”。
操作员超载的其他方法
在标准ML中,“平等类型”的机制大致对应于Haskell的内置类型Eq
,但是所有平等运算符都是由编译器自动派生的。程序员对流程的控制仅限于指定结构中的哪些类型组件是平等类型,哪些类型变量在相等性类型上的多态性类型范围内。
SML和OCAML的模块和函子可以扮演与Haskell类型类相似的角色,主要区别是类型推理的作用,这使得类型类别适合于临时多态性。 OCAML的面向对象的子集是另一种与类型类之一相媲美的方法。
相关概念
在C ++中,尤其是C ++ 20使用概念(C ++)支持类型类。作为例证,上述Haskell eq eq的Haskell示例将写为
template <typename T>
concept Equal =
requires (T a, T b) {
{ a == b } -> std::convertible_to<bool>;
{ a != b } -> std::convertible_to<bool>;
};
在干净的类型中,类似于Haskell,但语法略有不同。
汞具有类型,尽管它们与Haskell中的类型并不完全相同。
在Scala中,类型类是一个编程成语,可以使用现有语言功能(例如隐式参数,而不是单独的语言特征本身)来实现。由于它们在Scala中实现的方式,因此可以在歧义性的情况下明确指定代码中特定位置的类型类型的类型实例。但是,这不一定是一个好处,因为模棱两可的类型类实例可能容易出错。
证明助理COQ还支持最近版本中的类型类。与普通的编程语言不同,在COQ中,类型类定义中所述的类型类别(例如Monad Laws)的任何定律必须在使用这些类型类实例之前数学证明。
也可以看看
- 多态性(计算机科学) (其他类型的多态性)
- Haskell编程语言(首先设计类型类的语言)
- 操作员超载(一种类型类应用程序)
-
MONAD(功能编程) (
Monad
是类型类的示例) - 概念(C ++) (由于C ++ 20)
- 锈(编程语言)