函数的参数类型

Julia 允许给函数的(一部分或全部)参数加上类型约束,例如

1
2
3
function add2(x::Int64, y::Int64)
return x + y
end

正常使用例如

1
2
add2(1, 3) # 4
add2(Int64(1), Int64(10)) # 11

注意 Julia 不会对参数进行自动的类型转换,即使转换是安全无损的,例如

1
add2(Int32(1), Int32(2)) # error

使用过于具体的参数类型约束通常不是合适的选择,可以使用更抽象的类型约束,例如使用一般的整数类型

1
2
3
function add3(x::Integer, y::Integer)
return x + y
end

此时可以支持更多种类的参数进行调用,例如

1
2
add3(Int32(1), Int32(10)) # 11
add3(Int32(1), 10) # 11

注意到这里允许两个参数是不同的整数类型,Julia 提供了 where 关键字为泛型编程增加额外约束(类似 C++ 的 concepts 和 requires),例如要求两个参数的类型一致

1
2
3
function add4(x::T, y::T) where {T <: Integer}
return x + y
end

where 有各种灵活的语法,这里不作展开。

多态分发

Julia 允许对参数列表不同的同名函数提供不同的具体实现,例如

1
2
f(x::Int32) = Int32
f(x::Int64) = Int64

这里根据参数的不同类型返回对应的类型。Julia 得到了 f 的多种实现

1
f (generic function with 2 methods)

Julia 会智能地在实际使用中匹配不同的实现

1
2
3
4
5
julia> f(10)
Int64

julia> f(Int32(10))
Int32

这种机制被称为多态分发(动态分派):Julia 在调用函数时会根据形参列表(不含参数默认值)匹配最合适的实现。

注意:Julia 完全依赖形参列表来区分同名函数,如果新加上的一个实现和已有的某个实现的形参列表完全一致,那么会进行覆盖,并不会增加实现的数量。

这里的动态分派与 C++ 的函数重载非常相似,只不过 C++ 的重载决议发生在编译期,而 Julia 的多态分发发生在运行期。

使用 methods() 函数可以查询到 f 几种实现的更具体信息:例如刚刚在REPL中的定义的函数 f

1
2
3
4
5
6
julia> methods(f)
# 2 methods for generic function "f" from Main:
[1] f(x::Int64)
@ REPL[2]:1
[2] f(x::Int32)
@ REPL[1]:1

或者 methods() 这个内置函数自身的几种实现在源码中的位置

1
2
3
4
5
6
7
8
9
10
11
12
julia> methods(methods)
# 5 methods for generic function "methods" from Base:
[1] methods(f)
@ runtime_internals.jl:1377
[2] methods(f, t, mod::Module)
@ runtime_internals.jl:1365
[3] methods(f, mod::Union{Nothing, AbstractSet{Module}, Module, AbstractArray{Module}})
@ runtime_internals.jl:1377
[4] methods(f, t)
@ runtime_internals.jl:1358
[5] methods(f, t, mod::Union{Nothing, AbstractSet{Module}, Tuple{Module}, AbstractArray{Module}})
@ runtime_internals.jl:1358