Julia机器核心编程.多重分配
在开始深入探讨多重分派这个主题之前,我们先问自己一个简单的问题:分派到底是什么意思?用最简单的术语来解释,分派的意思就是发送!
在编程术语中,分派意味着向监听器发送一条消息或者调用一个函数。基本上就是将一段数据(或信息包)发送给准备用来处理它的代码。
分派有多种不同的类型,下面列举出其中的一部分:
· 静态分派:在编译时定义分派的顺序。事实上,在静态分派中,在程序执行之前所有类型都是已知的。编译器能够为每种可能的数据类型组合生成特定的代码,并提前知道它们的使用时间和位置。这是大多数语言中最常用的一种分派方式。举例来说,如果我们在代码的某个位置使用funct()或x.funct()调用函数或者方法,那么每次都会调用相同的函数或者方法,不会有任何变化
· 动态分派:可以在运行时定义分派顺序,这就意味着编译器必须拥有一个包含所有已定义函数的查找表,然后确定运行时实际调用哪些函数。用代码解释的话,假设有类classA和classB,并且它们各自都有一个名为foo()的函数实现,那么在运行时将检查这两个类,最后classA.foo()和classB.foo()的其中一个将会被调用。
· 多重分派:在多重分派中,分派顺序取决于函数名称以及所传递的参数类型,即函数的签名和被调用的实际实现是在运行时直接确定的。用代码解释的话,假设有一个类classA,它实现了一个方法foo(int),参数类型是一个整数,同时实现了另一个方法foo(char),参数类型是字符型。接下来,我们在程序中使用classA.foo(x)进行一次函数调用,在运行时检查将classA和x的类型,从而确定究竟应该调用哪一个foo()函数。
Julia支持多重分派,下面探讨Julia是如何实现此技术的。
方法是Julia生态系统中非常重要的一部分,为了更好地理解多重分派是什么,以及Julia使用多重分派的原因,我们首先需要知道方法是什么。
假设有一个对两个数字求和的函数。范例如下:
代码01~03行定义了求和函数,传入的参数类型被限制为整型。
这里我们定义了add_numbers()函数,它可以接收两个整型参数。当调用add_numbers(1,2)函数时,我们将会得到以下结果:
int64
就和我们预期的结果一样,1+2=3。另外,我们注意到ans的类型,也和所预期的一样是Int64。
但是如果不小心给函数传递了浮点数:
Julia将抛出一个错误!为什么?
答案很简单,因为在函数体中已经明确定义了会传递给add_numbers()函数两个Int64类型的参数。如果没有明确定义这两个参数必须是整数类型,那么就不会抛出错误,如下所示。
这似乎与Python中的函数用法非常相似,在Python中我们只是定义函数,并没有指定参数的类型,而是将推理参数类型的工作留给了Python解释器来做,Julia在这里所做的工作和Python解释器是一样的。
但是,这是否意味着我们之前对参数进行明确的类型定义是错误的呢?答案是否定的!
给函数指定所期望的参数类型会使它们运行得更快,因为编译器不用再推断提供给函数的参数的类型了。记住自己的写法,会提升性能,减少开销通过指定参数类型不仅可以防止编译器浪费时间,而且还可以获得速度上的提升。
我们回到函数add_numbers(num1::Int64,num2::Int64)上,在保证输入参数的类型是整数的情况下,如果想要这个函数返回一个Float类型的对象,该怎么办呢?一种方法是使用convert函数,它会对所传入的参数进行类型转换。convert函数可以接收两个参数,第一个参数是要转换成的数据的类型;第二个参数是准备转换的数据。
01 julia> function add_numbers(num1::Int64, num2::Int64)
02 float_num1 = convert(Abstract Float, num1)
03 float_num2 = convert(Abstract Float, num2)
04 return float_num1+float_num2
05 end
06 add_numbers (generic function with 1 method)
07
08 julia> add_numbers(4,6)
09 10.0
结果是正确的,但不是我们想要的。我们想要的是,即使提供了Float参数,也可以让add_numbers函数起作用。为了解决这个问题,我们再定义一个处理Float64类型数据的方法。
对两个浮点数求和
本例中定义了add_numbers函数用来对两个浮点数求和。
01 julia> function add_numbers(num1::Float64, num2::Float64)
02 return num1+num2
03 end
04 add_numbers (generic function with 2 methods)
05
06 julia> add_numbers(222.0,333.0)
07 555.0
与对两个整数求和的函数的唯一区别就是,我们限制所传入的参数类型为浮点型。如果细心的话,我们会发现在代码04行第一次出现了(generic function with 2 methods)。这是因为这两个函数是同名函数,只不过所传入的参数不同,那么这时新构建的用于计算两个浮点数的和的函数,就会自动成为add_numbers函数下的一个子方法。同时,之前定义的计算两个整数的函数也会变成一个子方法,这两个方法共享add_numbers函数名。
要查看函数自身的所有方法,我们可以使用methods函数。
method函数的使用
01 julia> methods(add_numbers)
02 # 2 methods for generic function "add_numbers":
03 [1] add_numbers(num1::Float64, num2::Float64) at REPL[2]:2
04 [2] add_numbers(num1::Int64, num2::Int64) at REPL[1]:2
仔细观察输出结果,它列出了到目前为止我们为函数定义的所有方法,包括处理两个整型参数的方法和处理两个浮点型参数的方法。
像这种多个子方法对应相同的函数名,并在调用时自动由Julia根据所传递的参数类型来调用相应方法的机制,就是我们所说的多重分派。