1999年9月19日
Julia语言非常擅长重用代码来处理不同的数据类型,这使您可以轻松定义新类型并使用现有函数来处理它们,或者为现有类型定义新函数。 可以工作multimethods,或多个调度。
中我们将比较Julia multimethod机制和来自"纯粹面向对象的语文"的更熟悉的函数重载机制,并尝试演示其优势。
重载函数和调度参数
函数重载机制的出现是为了使程序员的工作更容易。 多亏了他,平方根函数 sqrt 或加法运算符 + 它可以针对不同的数据类型重新定义,并根据参数列表强制执行不同的操作。 这种机制用于"纯OOP语言"(蟒蛇,C++或Delphi),其中重载函数和运算符是静态多态的工具。
*没有dispatching(C)的语言允许您只有一个函数实现中它们不允许您根据传递给它的参数通过相同的名称调用不同的函数。
*单个或多个(c++,Python)。..)他们依靠其中一个参数来确定被调用的函数的具体实现(第一,第二。..).
*具有多个调度(口齿不清的朱丽亚)的语言根据列表中的所有参数(第一个和第三个,或优先级的任何组合)选择函数的特定实现。
有节奏,但没有节制。 我们建议深入研究一个示例,该示例应显示多重调度机制的工作原理。
朱利亚*
中我们将保留类型和字段的英文名称,以简化与C源代码的比较,我们稍后也将编写C源代码。
首先,我们创建一个抽象类型。 Pet 我们从它继承类型。 Dog 和 Cat. 我们不能设置字段 name 一个抽象类型,所以我们为每个类设置这个字段,允许一些重复的代码。 然后我们定义一个通用函数 encounter,其描述了两种动物的相互作用。 首先,她发现哪个动词适用于这种交互,调用另一个通用函数。 meets,其尚未确定。
abstract type Pet end
struct Dog <: Pet; name::String end
struct Cat <: Pet; name::String end
# 我们可以定义一个处理父类型数据的方法。,
# 但它不会在我们的这个演示中被调用。
meets( c::Pet, d::Pet ) = "GENERIC"
function encounter( a::Pet, b::Pet )
verb = meets(a, b)
println( "$(a.name) встречает $(b.name) и $verb" )
end
最后一步是函数 encounter 打印事件报告:哪些动物遇到并替换必要的动词。
我们正在创建函数的四个变体 meets 使用不同的参数,我们定义将插入到最终报告(函数)中的不同动词 encounter 我们还有一个)。 根据两种动物的类型,动词会有所不同。
meets( a::Dog, b::Dog ) = "обнюхивает"
meets( a::Dog, b::Cat ) = "прогоняет"
meets( a::Cat, b::Dog ) = "шипит"
meets( a::Cat, b::Cat ) = "подкрадывается"
然后我们创建对象(特定宠物)的实例,然后调用函数 encounter 以他们为论据。
fido = Dog( "Fido" )
rex = Dog( "Rex" )
whiskers = Cat( "Whiskers" )
spots = Cat( "Spots" )
他说,朱利亚是一个热的热的热的热的热的热的热的热的热的热的热的热的热的热的热的热的热的热的热的热 字段不能在其中声明。 可能是什么原因?:
- 可以是一个类,而不是一个类。
- 或者说,如果你在抽象类中声明一个字段,那么子类将无法摆脱它。
- 或者,当在继承类型中定义字段时,代码变得更加自我记录。
的c++指针
示例示例。 它的代码在文件中 pets.cpp.
我们立即看到一个优点:在++中,您可以向抽象类型添加字段。 然后我们必须定义方法 meets 因为没有它,编译器会给我们一个错误。 我们不能重新定义一个尚未定义的函数。 没有它,编译器返回错误。 In function 'void encouter(Pet,Pet)': error: 'meets' was not declared in this scope.
class Pet{
public:
string name;
};
string meets( Pet a, Pet b ) {return "GENERIC"; }
void encounter( Pet a, Pet b ){
string verb = meets( a, b );
cout << a.name << " встречает " << b.name << " и " << verb << endl;
}
然后我们创建子类型。 Dog 和 Cat 无需在其定义中指定字段 name 因为抽象类有它。 下面我们重新定义了方法,但确切地说,这些不是方法,而是重载函数。
class Dog : public Pet {};
class Cat : public Pet {};
string meets( Dog a, Dog b ) { return "обнюхивает"; }
string meets( Dog a, Cat b ) { return "прогоняет"; }
string meets( Cat a, Dog b ) { return "шипит"; }
string meets( Cat a, Cat b ) { return "подкрадывается"; }
和功能内部 main 我们创建对象的所有实例,给它们命名,并确定调用时会发生什么。 encounter 以不同的宠物为论据。
int main() {
Dog fido; fido.name = "Fido";
Dog rex; fido.name = "Rex";
Cat whiskers; whiskers.name = "Whiskers";
Cat spots; spots.name = "Spots";
encounter( fido, rex );
encounter( fido, whiskers );
encounter( whiskers, rex );
encounter( whiskers, spots );
return 0;
}
让我们编译这段代码。 结果,我们应该得到一个二进制文件。 pets,从而可以运行并且可以接收输出。
;g++ pets.cpp -o pets
实现的比较
现在让我们比较一下多重调度机制和它设计开发的函数重载机制的操作。
1. 朱利亚*
我使用朱利亚数据设置为"软件" encounter 让我们看看输出。
encounter( fido, rex )
encounter( fido, whiskers )
encounter( whiskers, rex )
encounter( whiskers, spots )
一切都如我们所料。
2. 的c++指针
让我们运行编译成二进制文件的代码。 pets.
;./pets
为什么会这样
这里的瓶颈是功能 encounter( Pet a, Pet b ). 我们可以重新定义函数 encounter 对于所有可能的参数:
void encounter( Dog a, Cat b ){
string verb = meets( a, b );
cout << a.name << " встречает " << b.name << " и " << verb << endl;
}
但这只会表明我们不能继承新数据类型的行为。 要在C++。动态多态中了虚拟方法,但也需要在每个子类中重新定义它们,这也不会简化代码继承。
结论
函数重载机制不允许使用动态定义的类型,所有类型都是静态定义的。 Julia使用多重分派机制来解决继承问题,我们已经证明Julia中的继承对于父数据类型和子数据类型都非常有效。
这个事实就是事实.: The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski | JuliaCon 2019.