多分派
多态 |
---|
特设多态 |
參數多态 |
子类型 |
多分派或译多重派发(multiple dispatch)或多方法(multimethod),是某些编程语言的一个特性,其中的函数或者方法,可以在运行时间(动态的)基于它的实际参数的类型,或在更一般的情况下于此之外的其他特性,来动态分派[1]。这是对单分派多态的推广, 那里的函数或方法调用,基于在其上调用方法的对象的派生类型,而动态分派。多分派使用一个或多个实际参数的组合特征,路由动态分派至实现函数或方法。
理解分派
[编辑]软件工程师通常把代码写进代码块中,代码块通常称作过程、函数、方法。代码通过被调用来执行,调用时将控制权传入函数中,当函数执行完成后将控制权返回给调用者。
函数名通常用来描述函数的目的。有时会将多个函数起同样的名称。比如同名函数在逻辑上处理相同的任务,但是操作在不同类型的输入值上。在这种情况下,无法仅仅通过函数名,来判断目标代码块。那么,函数的实际参数的个数和类型,也就被用来判断。
通常,单分派面向对象语言,在调用一个方法时,方法参数中一个参数会被特殊对待,并用来决定哪一个方法(如果有多个同名方法)会被调用。在许多语言中,这个特殊的参数是在语法上指明的,许多编程语言在调用方法时,把特殊参数放在小圆点(.
)之前。例如 special.method(other, arguments, here)
,这样 lion.sound()
将会发出狮吼,同时 sparrow.sound()
只会吱吱地叫。一般来说,对于面向对象的编程语言,这个小圆点之前的参数(上例中的lion
和sparrow
)被称为接收者[2]。
相反,在实现了多分派的语言中,被调用的函数,即是那些参数个数一样多,并且类型也匹配的调用。在调用中并没有特殊参数,来决定那个方法被调用。也就是说,所有参数的运行时类型都参与分派。CLOS是早期和著名的多分派语言。
数据类型
[编辑]对于编译时间可以区分数据类型的编程语言,在交替(alternative)函数中进行选择,可以发生在编译时间,创建交替函数用于编译时间选择的活动,通常被叫做函数重载。
在有些编程语言中,这种数据类型的识别,可以被延后至运行时间(后期绑定)。交替函数的选择发生在运行时间,并依据动态确定的函数实际参数的类型。以这种方式选择交替实现的函数,通常被称为多方法。
例子
[编辑]可以通过例子更加清晰的区分多分派和单一分派。假想一个游戏,它有两种(用户可见的)物体:飞船和小行星。当两个物体要相撞的时候,程序需要依据什么物体要相撞而做不同的事情。
具有内建多分派的语言
[编辑]Common Lisp
[编辑]在具有多分派的Common Lisp语言中,可以在Common Lisp对象系统中如下这样实现:
(defgeneric collide (x y))
(defclass asteroid () ())
(defclass spaceship () ())
(defmethod collide-with ((x asteroid) (y asteroid))
;; deal with asteroid hitting asteroid
)
(defmethod collide-with ((x asteroid) (y spaceship))
;; deal with asteroid hitting spaceship
)
(defmethod collide-with ((x spaceship) (y asteroid))
;; deal with spaceship hitting asteroid
)
(defmethod collide-with ((x spaceship) (y spaceship))
;; deal with spaceship hitting spaceship
)
并且对其他方法也是类似的。没有使用显式测试和“动态转换”。
由于多分派的存在,方法要定义在类中并包含在对象中的传统想法,变得不再吸引人了,上述每个collide-with
方法,都附属于两个不同的类而非一个类。因此方法调用的特殊语法,一般会消失,从而方法调用看起来完全就像正常的函数调用,并且方法被组织入泛化函数而非类中。
Julia
[编辑]Julia有内建的多分派,并且它是语言设计的中心[3]。Julia版本的例子如下:
collide_with(x::Asteroid, y::Asteroid) = ... # deal with asteroid hitting asteroid
collide_with(x::Asteroid, y::Spaceship) = ... # deal with asteroid hitting spaceship
collide_with(x::Spaceship, y::Asteroid) = ... # deal with spaceship hitting asteroid
collide_with(x::Spaceship, y::Spaceship) = ... # deal with spaceship hitting spaceship
C#
[编辑]C#在版本4(2010年4月),使用关键字dynamic
,介入了对动态多方法的支持[4]。下面的例子展示多方法协同于在版本8(2019年9月)中介入的switch
表达式[5]。像很多其他静态类型的语言语言一样,C#还支持静态方法重载[6],Microsoft预期开发者在多数场景下会选用静态类型超过动态类型[7]。dynamic
关键字支持COM对象和动态类型的.NET语言的互操作。
class Program
{
static void Main()
{
Console.WriteLine(Collider.Collide(new Asteroid(101), new Spaceship(300)));
Console.WriteLine(Collider.Collide(new Asteroid(10), new Spaceship(10)));
Console.WriteLine(Collider.Collide(new Spaceship(101), new Spaceship(10)));
}
}
static class Collider
{
public static string Collide(SpaceObject x, SpaceObject y) =>
((x.Size > 100) && (y.Size > 100)) ?
"Big boom!" : CollideWith(x as dynamic, y as dynamic);
private static string CollideWith(Asteroid x, Asteroid y) => "a/a";
private static string CollideWith(Asteroid x, Spaceship y) => "a/s";
private static string CollideWith(Spaceship x, Asteroid y) => "s/a";
private static string CollideWith(Spaceship x, Spaceship y) => "s/s";
}
abstract class SpaceObject
{
public SpaceObject(int size) => Size = size;
public int Size { get; }
}
class Asteroid : SpaceObject
{
public Asteroid(int size) : base(size) { }
}
class Spaceship : SpaceObject
{
public Spaceship(int size) : base(size) { }
}
输出:
big-boom
a/s
s/s
Groovy
[编辑]Groovy是通用的Java兼容/互用的JVM语言,它对立于Java,使用后期绑定/多分派[8]。
/*
Groovy implementation of C# example above
Late binding works the same when using non-static methods or compiling class/methods statically
(@CompileStatic annotation)
*/
class Program {
static void main(String[] args) {
println Collider.collide(new Asteroid(101), new Spaceship(300))
println Collider.collide(new Asteroid(10), new Spaceship(10))
println Collider.collide(new Spaceship(101), new Spaceship(10))
}
}
class Collider {
static String collide(SpaceObject x, SpaceObject y) {
(x.size > 100 && y.size > 100) ? "big-boom" : collideWith(x, y) // Dynamic dispatch to collideWith method
}
private static String collideWith(Asteroid x, Asteroid y) { "a/a" }
private static String collideWith(Asteroid x, Spaceship y) { "a/s" }
private static String collideWith(Spaceship x, Asteroid y) { "s/a" }
private static String collideWith(Spaceship x, Spaceship y) { "s/s"}
}
class SpaceObject {
int size
SpaceObject(int size) { this.size = size }
}
@InheritConstructors class Asteroid extends SpaceObject {}
@InheritConstructors class Spaceship extends SpaceObject {}
用多分派库扩展的语言
[编辑]在不于语言定义或语法层次支持多分派的语言中,可能经常使用库扩展来增加多分派。
JavaScript
[编辑]JavaScript和TypeScript不在语言语法层次上支持多方法,但可以通过库来增加多分派。例如,使用multimethod包[9],它提供了多分派、泛化函数的实现。JavaScript的动态类型版本:
import { multi, method } from '@arrows/multimethod'
class Asteroid {}
class Spaceship {}
const collideWith = multi(
method([Asteroid, Asteroid], (x, y) => {
// deal with asteroid hitting asteroid
}),
method([Asteroid, Spaceship], (x, y) => {
// deal with asteroid hitting spaceship
}),
method([Spaceship, Asteroid], (x, y) => {
// deal with spaceship hitting asteroid
}),
method([Spaceship, Spaceship], (x, y) => {
// deal with spaceship hitting spaceship
}),
)
TypeScript有对应的静态类型版本。[a]
Python
[编辑]可以使用库扩展来向Python增加多分派。例如,最早的模块multimethods.py[10],它为Python提供了CLOS风格的多方法而不用变更语言的底层语法或关键字。在功能上,这非常类似于CLOS例子,但是语法是常规Python的。[b]
Guido van Rossum使用Python 2.4介入的修饰器(decorator),出品了多方法的具有简化了的语法的一个简单实现,他为此定义了multimethod
修饰器[11]。[c]
multipledispatch[12]采用的形式与之一致。
模块multimethod[13],采用了修饰器和Python 3.5介入的类型提示实现多方法:
from multimethod import multimethod
class Asteroid(): pass
class Spaceship(): pass
@multimethod
def collide_with(x: Asteroid, y: Asteroid):
'''deal with asteroid hitting asteroid'''
print("asteroid hitting asteroid")
@multimethod
def collide_with(x: Asteroid, y: Spaceship):
'''deal with asteroid hitting spaceship'''
print("asteroid hitting spaceship")
@multimethod
def collide_with(x: Spaceship, y: Asteroid):
'''deal with spaceship hitting asteroid'''
print("spaceship hitting asteroid")
@multimethod
def collide_with(x: Spaceship, y: Spaceship):
'''deal with spaceship hitting spaceship'''
print("spaceship hitting spaceship")
>>> a = Asteroid()
>>> b = Spaceship()
>>> collide_with(a, b)
asteroid hitting spaceship
此外,还有PEAK-Rules包提供语法类似上述例子的多分派[14],它后来被替代为PyProtocols[15]。Reg库也支持多分派和谓词分派[16]。
C
[编辑]C语言使用C Object System库[17],可以支持类似于CLOS的动态分派。它是完全可扩展的并且方法不需要任何的手工处理。动态消息(方法)通过COS分派器来分派,它比Objective-C更快。下面是使用COS的例子:
#include <stdio.h>
#include <cos/Object.h>
#include <cos/gen/object.h>
/* 类 */
defclass (Asteroid)
/* 数据成员 */
endclass
defclass (Spaceship)
/* 数据成员 */
endclass
/* 泛化函数 */
defgeneric (_Bool, collide_with, _1, _2);
/* 多方法 */
defmethod (_Bool, collide_with, Asteroid, Asteroid)
/* deal with asteroid hitting asteroid */
endmethod
defmethod (_Bool, collide_with, Asteroid, Spaceship)
/* deal with asteroid hitting spaceship */
endmethod
defmethod (_Bool, collide_with, Spaceship, Asteroid)
/* deal with spaceship hitting asteroid */
endmethod
defmethod (_Bool, collide_with, Spaceship, Spaceship)
/* deal with spaceship hitting spaceship */
endmethod
/* 用例 */
int main(int argc, char *argv[])
{
OBJ a = gnew(Asteroid);
OBJ s = gnew(Spaceship);
printf("<a,a> = %d\n", collide_with(a, a));
printf("<a,s> = %d\n", collide_with(a, s));
printf("<s,a> = %d\n", collide_with(s, a));
printf("<s,s> = %d\n", collide_with(s, s));
grelease(a);
grelease(s);
}
编程语言支持
[编辑]主范型
[编辑]支持通用的多方法
[编辑]通过扩展
[编辑]- 任何.NET语言(通过库MultiMethods.NET[37])
- C(通过库C Object System[17])
- C#(通过库multimethod-sharp[38])
- C++(通过库yomm2[39]和multimethods[40])
- D(通过库openmethods[41])
- Factor(通过标准multimethods词汇表[42])
- Java(使用扩展MultiJava[43])
- JavaScript(通过包@arrows/multimethod[9])
- Perl(通过模块Class::Multimethods[44])
- Python(模块multimethod[13]、multipledispatch[12]或Reg库[16])
- Racket(通过库multimethod-lib[45])
- Ruby(通过Multiple Dispatch库[46]、Multimethod包[47]和Vlx-Multimethods包[48])
- Scheme(通过TinyCLOS[49])
- TypeScript(通过包@arrows/multimethod[9])
模拟多分派
[编辑]C
[编辑]C语言没有动态分派,也可以不使用C Object System库,而以某种形式手工实现。动态分派经常使用enum
来标识一个对象的子类型,然后可通过在函数指针分支表中查找这个值来完成。C语言模拟多方法的简单例子:
typedef void (*CollisionCase)(void);
void collision_AA(void) { /* handle Asteroid-Asteroid collision */ };
void collision_AS(void) { /* handle Asteroid-Spaceship collision */ };
void collision_SA(void) { /* handle Spaceship-Asteroid collision */ };
void collision_SS(void) { /* handle Spaceship-Spaceship collision*/ };
typedef enum {
THING_ASTEROID = 0,
THING_SPACESHIP,
THING_COUNT /* not a type of thing itself, instead used to find number of things */
} Thing;
CollisionCase collisionCases[THING_COUNT][THING_COUNT] = {
{&collision_AA, &collision_AS},
{&collision_SA, &collision_SS}
};
void collide(Thing a, Thing b) {
(*collisionCases[a][b])();
}
int main(void) {
collide(THING_SPACESHIP, THING_ASTEROID);
}
Java
[编辑]在只有单一分派的语言比如Java中,多分派可以用多层单一分派来模拟:
interface Collideable {
void collideWith(final Collideable other);
/* These methods would need different names in a language without method overloading. */
void collideWith(final Asteroid asteroid);
void collideWith(final Spaceship spaceship);
}
class Asteroid implements Collideable {
public void collideWith(final Collideable other) {
// Call collideWith on the other object.
other.collideWith(this);
}
public void collideWith(final Asteroid asteroid) {
// Handle Asteroid-Asteroid collision.
}
public void collideWith(final Spaceship spaceship) {
// Handle Asteroid-Spaceship collision.
}
}
class Spaceship implements Collideable {
public void collideWith(final Collideable other) {
// Call collideWith on the other object.
other.collideWith(this);
}
public void collideWith(final Asteroid asteroid) {
// Handle Spaceship-Asteroid collision.
}
public void collideWith(final Spaceship spaceship) {
// Handle Spaceship-Spaceship collision.
}
}
运行时间instanceof
检查可以在一个或两个层次上使用。
代码示例
[编辑]- ^
TypeScript的多方法示例:
import { multi, method, Multi } from '@arrows/multimethod' class Asteroid {} class Spaceship {} type CollideWith = Multi & { (x: Asteroid, y: Asteroid): void (x: Asteroid, y: Spaceship): void (x: Spaceship, y: Asteroid): void (x: Spaceship, y: Spaceship): void } const collideWith: CollideWith = multi( method([Asteroid, Asteroid], (x, y) => { // deal with asteroid hitting asteroid }), method([Asteroid, Spaceship], (x, y) => { // deal with asteroid hitting spaceship }), method([Spaceship, Asteroid], (x, y) => { // deal with spaceship hitting asteroid }), method([Spaceship, Spaceship], (x, y) => { // deal with spaceship hitting spaceship }), )
- ^
Python的multimethods.py示例:
from multimethods import Dispatch from game_objects import Asteroid, Spaceship from game_behaviors import as_func, ss_func, sa_func collide = Dispatch() collide.add_rule((Asteroid, Spaceship), as_func) collide.add_rule((Spaceship, Spaceship), ss_func) collide.add_rule((Spaceship, Asteroid), sa_func) def aa_func(a, b): """Behavior when asteroid hits asteroid.""" # ...define new behavior... collide.add_rule((Asteroid, Asteroid), aa_func)
# ...later... collide(thing1, thing2)
- ^
Python的van Rossum最初的多方法实现:
@multimethod(Asteroid, Asteroid) def collide(a, b): """Behavior when asteroid hits a asteroid.""" # ...define new behavior... @multimethod(Asteroid, Spaceship) def collide(a, b): """Behavior when asteroid hits a spaceship.""" # ...define new behavior... # ... define other multimethod rules ...
引用
[编辑]- ^ Ranka, Sanjay; Banerjee, Arunava; Biswas, Kanad Kishore; Dua, Sumeet; Mishra, Prabhat; Moona, Rajat. Contemporary Computing: Second International Conference, IC3 2010, Noida, India, August 9–11, 2010. Proceedings. Springer. 2010-07-26 [2021-03-23]. ISBN 9783642148248. (原始内容存档于2021-04-27).
- ^ Igor Wojda. Programmer dictionary: Receiver. Kt.Academy. 2018-02-08 [2020-02-27] (英语).
- ^ Bezanson, Jeff; Edelman, Alan; Karpinski, Stefan; Shah, Viral B. Julia: A fresh approach to numerical computing. SIAM Review. 7 February 2017, 59 (1): 65–98. arXiv:1411.1607 . doi:10.1137/141000671.
- ^ Using type dynamic (C# Programming Guide). [2020-05-14]. (原始内容存档于2021-05-26).
- ^ switch expression (C# reference). [2020-05-14]. (原始内容存档于2021-06-28).
- ^ Basic concepts. [2020-05-14]. (原始内容存档于2021-04-16).
- ^ Dynamic .NET - Understanding the Dynamic Keyword in C# 4. [2020-05-14]. (原始内容存档于2021-05-24).
- ^ Groovy - Multi-methods. [2021-04-15]. (原始内容存档于2021-04-16).
- ^ 9.0 9.1 9.2 @arrows/multimethod (页面存档备份,存于互联网档案馆) Multiple dispatch in JavaScript/TypeScript with configurable dispatch resolution by Maciej Cąderek.
- ^ multimethods.py (页面存档备份,存于互联网档案馆), Multiple dispatch in Python with configurable dispatch resolution by David Mertz, et al.
- ^ Five-minute Multimethods in Python. [2014-07-13]. (原始内容存档于2021-05-29).
- ^ 12.0 12.1 multipledispatch. [2021-04-15]. (原始内容存档于2020-11-11).
- ^ 13.0 13.1 Coady, Aric, multimethod: Multiple argument dispatching., [2021-01-28], (原始内容存档于2020-12-31)
- ^ PEAK-Rules 0.5a1.dev. Python Package Index. [21 March 2014]. (原始内容存档于2017-03-14).
- ^ PyProtocols. Python Enterprise Application Kit. [26 April 2019]. (原始内容存档于2021-05-05).
- ^ 16.0 16.1 Reg. Read the docs. [26 April 2019]. (原始内容存档于2021-03-05).
- ^ 17.0 17.1 C Object System: A framework that brings C to the level of other high level programming languages and beyond: CObjectSystem/COS. 2019-02-19 [2021-03-30]. (原始内容存档于2021-05-01).
- ^ Methods. The Julia Manual. Julialang. [11 May 2014]. (原始内容存档于2016-07-17).
- ^ Multimethods in C# 4.0 With 'Dynamic'. [2009-08-20]. (原始内容存档于2009-08-25).
- ^ Cecil Language. [2008-04-13]. (原始内容存档于2016-09-01).
- ^ Multimethods in Clojure. [2008-09-04]. (原始内容存档于2015-09-20).
- ^ Steele, Guy L. 28. Common LISP: The Language. Bedford, MA, U.S.A: Digital Press. 1990 [2021-03-30]. ISBN 978-1-55558-041-4. (原始内容存档于2017-12-17).
- ^ Background and Goals. [2008-04-13]. (原始内容存档于2020-04-04).
- ^ The Fortress Language Specification, Version 1.0 (PDF). [2010-04-23]. (原始内容 (PDF)存档于2013-01-20).
- ^ Multimethods in Groovy. [2008-04-13]. (原始内容存档于2011-08-12).
- ^ Methods – LassoGuide 9.2. [2014-11-11]. (原始内容存档于2021-06-13).
- ^ Visitor Pattern Versus Multimethods. [2008-04-13]. (原始内容存档于2021-02-05).
- ^ Nim Manual: Multi-methods. [2020-09-11]. (原始内容存档于2021-06-15).
- ^ Perl 6 FAQ. [2008-04-13]. (原始内容存档于2012-03-13).
- ^ How S4 Methods Work (PDF). [2008-04-13]. (原始内容存档 (PDF)于2021-05-10).
- ^ Multiple Dispatch in Seed7. [2011-04-23]. (原始内容存档于2021-01-29).
- ^ TADS 3 System Manual. [2012-03-19]. (原始内容存档于2017-02-14).
- ^ VB.Net Multiple Dispatch. [2020-03-31]. (原始内容存档于2021-04-11).
- ^ New Features in C#4.0 and VB.Net 10.0. [2020-03-31]. (原始内容存档于2021-02-01).
- ^ Notes for Programming Language Experts. [2016-08-21]. (原始内容存档于2021-06-26).
- ^ Multiple dispatch. [2021-03-30]. (原始内容存档于2021-05-08).
- ^ MultiMethods.NET. [2014-07-13]. (原始内容存档于2010-02-26).
- ^ multimethod-sharp. [2021-03-30]. (原始内容存档于2016-01-23).
- ^ yomm2. [2021-03-30]. (原始内容存档于2020-11-12).
- ^ multimethods. [2021-03-30]. (原始内容存档于2020-11-22).
- ^ openmethods
- ^ multimethods vocabulary. [2014-07-13]. (原始内容存档于2021-04-29).
- ^ MultiJava. [2014-07-13]. (原始内容存档于2021-04-11).
- ^ Class::Multimethods. [2014-07-13]. (原始内容存档于2013-10-21).
- ^ multimethod-lib. [2021-03-30]. (原始内容存档于2021-04-29).
- ^ The Multiple Dispatch Library. [2021-03-30]. (原始内容存档于2021-04-30).
- ^ Multimethod Package. [2021-03-30]. (原始内容存档于2021-04-29).
- ^ Vlx-Multimethods Package. [2021-03-30]. (原始内容存档于2021-04-27).
- ^ TinyCLOS. [2014-07-13]. (原始内容存档于2008-12-11).
外部链接
[编辑]- Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter. Open Multi-Methods for C++ (PDF). ACM 6th International Conference on Generative Programming and Component Engineering. 2007 [2014-07-13]. (原始内容存档 (PDF)于2021-04-29).
- Dynamic multiple dispatch. docs.racket-lang.org. [2018-03-12]. (原始内容存档于2021-04-29).