通用代码,也叫通用编程,是一种计算机编程风格,其中算法是根据待指定的类型编写的,然后在需要时作为参数提供的特定类型进行实例化。 这种方法由ML于1973年开创,允许编写仅在使用时操作的类型集不同的共同功能或类型,从而减少重复。 这些软件实体在Ada,C#, Delphi,Eiffel,F#,Java,Rust,Swift,TypeScript和Visual Basic .NET中称为泛型。 它们被称为ML,Scala,Haskell中的参数多态性(Haskell社区也使用术语“泛型”用于相关但有些不同的概念)和Julia; C ++和D中的模板; 有影响力的1994年着作“设计模式”中的参数化类型。设计模式的作者注意到这种技术,特别是与委托相结合时,非常强大,但是,
通用性
至少在20世纪70年代,通用设施已经存在于ML,CLU和Ada等语言中,并且随后被许多基于对象和面向对象的语言所采用,包括BETA,C ++,D,Eiffel,Java,和DEC已经不复存在的Trellis-Owl语言。
在各种编程语言中以不同方式实现和支持通用性;术语“通用”在各种编程环境中也被不同地使用。例如,在Forth中,编译器可以在编译时执行代码,并且可以动态地为这些单词创建新的编译器关键字和新实现。它几乎没有暴露编译器行为的单词,因此自然地提供了通用性能力,然而,在大多数Forth文本中并未提及这些能力。类似地,动态类型语言,特别是解释型语言,默认情况下通常提供通用性,因为向函数和值赋值传递值都是类型无关紧要的,并且这种行为通常用于抽象或代码简洁,但是这通常不是标记为通用性,因为它是语言所采用的动态类型系统的直接后果。该术语已用于函数式编程,特别是在类似Haskell的语言中,它使用结构类型系统,其中类型总是参数化的,并且这些类型上的实际代码是通用的。这些用法仍然用于代码保存和抽象呈现的类似目的。
可以将数组和结构视为预定义的泛型类型。数组或结构类型的每次使用都会实例化一个新的具体类型,或者重用先前实例化的类型。数组元素类型和结构元素类型是参数化类型,用于实例化相应的泛型类型。所有这些通常都内置在编译器中,语法与其他通用结构不同。一些可扩展的编程语言尝试统一内置和用户定义的泛型类型。
面向对象的代码
在使用静态类型语言创建容器类时,为每个包含的数据类型编写特定实现是不方便的,特别是如果每个数据类型的代码实际上是相同的。 例如,在C ++中,可以通过定义类模板来规避这种代码重复:
在上面,T是占位符,用于创建列表时指定的任何类型。 这些“tank-of-type-T”(通常称为模板)允许使用不同的数据类型重用类,只要保留某些合同(如子类型和签名)即可。 这种通用性机制不应与包含多态性相混淆,包含多态性是可交换子类的算法用法:例如,包含类型为Animal和Car的对象的Moving_Object类型的对象列表。 模板也可用于与类型无关的功能,如下面的Swap示例所示:
上面使用的C ++模板构造被广泛引用[引用需要]作为通用性构造,在程序员和语言设计者之间普及概念并支持许多通用编程习语。 D编程语言还提供了基于C ++先例但具有简化语法的完全通用的模板。 自J2SE 5.0引入以来,Java编程语言在语法上提供了基于C ++的通用性设施。
C#2.0,Oxygene 1.5(也称为Chrome)和Visual Basic .NET 2005具有利用自2.0版以来对Microsoft .NET Framework中存在的泛型的支持的构造。
用C ++进行扩展
C ++使用模板来启用通用编程技术。 C ++标准库包括
标准模板库或STL,它为常见的数据结构和算法提供模板框架。 C ++中的模板也可用于模板元编程,这是一种在编译时而不是运行时预先评估某些代码的方法。 使用模板专业化,C ++模板被认为是图灵完整的。
D中的模板
D编程语言支持基于C ++设计的模板。大多数C ++模板习语都会在没有改动的情况下转移到D,但D增加了一些额外的功能:
D中的模板参数不仅限于类型和原始值,还允许任意编译时值(如字符串和结构文字),以及任意标识符的别名,包括其他模板或模板实例。
1、模板约束和静态if语句提供了替代C ++的替换失败不是错误(SFINAE)机制,类似于C ++概念。
2、 is(...)表达式允许推测实例化在编译时验证对象的特征。
3、 auto关键字和typeof表达式允许对变量声明和函数返回值进行类型推断,这反过来允许“Voldemort类型”(没有全局名称的类型)。
D中的模板使用与C ++不同的语法:而在C ++中,模板参数包含在尖括号中(Template
), D使用惊叹号和括号:Template!(param1,param2)。这避免了由于比较运算符的模糊性导致的C ++解析困难。如果只有一个参数,则可以省略括号。通常,D结合上述特征以使用基于特征的通用编程来提供编译时多态性。例如,输入范围定义为满足isInputRange执行的检查的任何类型,其定义如下:
然后,只接受输入范围的函数可以在模板约束中使用上述模板:
功能
Haskell中的通用性
Haskell的类型类机制支持泛型编程。 Haskell中的六个预定义类型类(包括Eq,可以比较相等的类型和Show,其值可以呈现为字符串的类型)具有支持派生实例的特殊属性。 这意味着定义新类型的程序员可以声明此类型是这些特殊类型类之一的实例,而不提供在声明类实例时通常需要的类方法的实现。 所有必要的方法都将“派生” - 即根据类型的结构自动构造。 例如,以下一类二叉树的声明声明它是Eq和Show类的一个实例:
这导致相等函数(==)和字符串表示函数(show)被自动定义为任何类型的表单BinTree T,前提是T本身支持这些操作。
对Eq和Show的派生实例的支持使得它们的方法==并以与参数多态函数在质量上不同的方式显示泛型:这些“函数”(更准确地说,类型索引的函数族)可以应用于值 各种类型,虽然它们对于每种参数类型的行为都不同,但是需要很少的工作来添加对新类型的支持。 Ralf Hinze(2004)已经表明,通过某些编程技术可以为用户定义的类型类实现类似的效果。 其他研究人员已经在Haskell和Haskell的扩展(下面讨论)的上下文中提出了这种和其他类型的通用性的方法。
PolyP
PolyP是Haskell的第一个通用编程语言扩展。 在PolyP中,泛型函数称为polytypic。 该语言引入了一种特殊的构造,其中可以通过对常规数据类型的模式函子的结构的结构归纳来定义这种多型函数。 PolyP中的常规数据类型是Haskell数据类型的子集。 常规数据类型t必须是kind *→*,如果a是定义中的形式类型参数,则对t的所有递归调用必须具有t a形式。 这些限制排除了较高级别的数据类型以及嵌套数据类型,其中递归调用具有不同的形式。 PolyP中的展平功能在此处作为示例提供:
通用Haskell
Generic Haskell是
荷兰乌得勒支大学开发的Haskell的另一个扩展。它提供的扩展是:
类型索引值被定义为在各种Haskell类型构造函数(单元,基元类型,总和,产品和用户定义的类型构造函数)上索引的值。此外,我们还可以使用构造函数案例为特定构造函数指定类型索引值的行为,并使用默认情况在另一个中重用一个泛型定义。
例如,Generic Haskell中的相等函数:
其他通用代码
ML系列编程语言通过参数多态和支持仿函数的通用模块支持泛型编程。 标准ML和OCaml都提供了类似于类模板和Ada通用包的仿函数。 Scheme语法抽象也与泛型有关 - 这些实际上是模板化C ++的超集。
Verilog模块可以采用一个或多个参数,在模块实例化时将其实际值分配给该参数。 一个示例是
通用寄存器阵列,其中阵列宽度通过参数给出。 这种阵列与通用线矢量相结合,可以使单个模块实现中的任意位宽的通用缓冲器或存储器模块成为可能。
VHDL源自Ada,也具有通用能力。