Julia 教程 / 函数与多重派发
函数与多重派发
1. 函数定义
Julia 中函数是一等公民,有多种定义方式。
function/end 块
function greet(name)
return "Hello, $name!"
end
greet("Julia") # "Hello, Julia!"
一行定义(赋值形式)
greet(name) = "Hello, $name!"
# 等价于上面的 function/end 形式
空参数函数
function say_hi()
return "Hi there!"
end
# 调用时必须加括号
say_hi() # "Hi there!"
# say_hi # 返回函数对象本身
返回值
# 显式 return
function add(a, b)
return a + b
end
# 最后一个表达式的值自动返回(无需 return)
function add2(a, b)
a + b
end
# 返回多个值(实际返回 Tuple)
function divrem(a, b)
return a ÷ b, a % b
end
q, r = divrem(17, 5) # q=3, r=2
💡 提示: 在长函数中建议使用显式
return,短函数(一行或几行)可省略以保持简洁。
2. 匿名函数
# lambda 语法
f = x -> x^2 + 1
f(3) # 10
# 多参数
g = (x, y) -> x + y
g(3, 4) # 7
# 多行匿名函数
h = function(x)
if x > 0
return sqrt(x)
else
return 0.0
end
end
# 用作高阶函数参数
map(x -> x^2, [1, 2, 3, 4]) # [1, 4, 9, 16]
filter(x -> x > 2, [1, 2, 3, 4]) # [3, 4]
sortby(x -> abs(x), [-3, 1, -2]) # [1, -2, -3]
# do 块语法(将匿名函数作为第一个参数)
open("data.txt") do io
read(io, String)
end
# 等价于
open(io -> read(io, String), "data.txt")
3. 关键字参数
关键字参数使用 ; 分隔,调用时需指定参数名:
# 定义带关键字参数的函数
function plot_data(x, y; color="blue", linewidth=2, title="")
println("Color: $color, Width: $linewidth, Title: $title")
# ... 绘图逻辑
end
# 调用
plot_data([1,2,3], [4,5,6])
plot_data([1,2,3], [4,5,6], color="red", linewidth=3)
plot_data([1,2,3], [4,5,6]; title="My Plot") # 也可用 ; 分隔
# 关键字参数可以有默认值
function config(; host="localhost", port=8080, debug=false)
return (host=host, port=port, debug=debug)
end
config() # (host="localhost", port=8080, debug=false)
config(port=3000, debug=true) # (host="localhost", port=3000, debug=true)
收集关键字参数(kwargs...)
function flexible(; kwargs...)
for (key, value) in kwargs
println("$key = $value")
end
end
flexible(name="test", count=42, flag=true)
# name = test
# count = 42
# flag = true
⚠️ 注意: 位置参数(positional arguments)和关键字参数(keyword arguments)用
;分隔。;之前的是位置参数,之后的是关键字参数。
4. 可变参数(Varargs)
# 可变位置参数
function my_sum(xs...)
result = 0
for x in xs
result += x
end
return result
end
my_sum(1, 2, 3) # 6
my_sum(1, 2, 3, 4, 5) # 15
# 可变参数在函数内部是 Tuple
function show_args(args...)
println(typeof(args)) # Tuple{Int64, Float64, String}
for (i, arg) in enumerate(args)
println("Arg $i: $arg ($(typeof(arg)))")
end
end
show_args(42, 3.14, "hello")
# 混合使用
function log_message(level, messages...)
println("[$level] ", join(messages, " "))
end
log_message("INFO", "Server", "started", "on", "port", "8080")
# [INFO] Server started on port 8080
# 展开参数(splatting)
nums = [1, 2, 3, 4, 5]
my_sum(nums...) # 将数组展开为参数
5. 默认参数值
function power(base, exp=2)
return base ^ exp
end
power(3) # 9(使用默认 exp=2)
power(3, 3) # 27
# 多个默认值
function connect(host="localhost", port=5432, user="admin")
println("Connecting to $user@$host:$port")
end
connect() # admin@localhost:5432
connect("db.example.com") # [email protected]:5432
connect("db.example.com", 3306) # [email protected]:3306
connect("db.example.com", 3306, "root") # [email protected]:3306
# 默认值可以是表达式
function create_file(path, content="")
# content 的默认值在每次调用时求值
return (path=path, content=content)
end
💡 提示: 默认参数必须从右到左连续。不能只跳过中间的默认参数而指定后面的参数,这时应使用关键字参数。
6. 多重派发(Multiple Dispatch)
多重派发是 Julia 最核心的特性:函数根据所有参数的类型选择方法,而非仅根据第一个参数(如面向对象语言的 this)。
基本示例
# 定义同名函数的不同方法
function collide(x::Int, y::Int)
println("Int vs Int: 数值碰撞检测")
end
function collide(x::String, y::String)
println("String vs String: 文本匹配检测")
end
function collide(x::Int, y::String)
println("Int vs String: 类型不匹配,跳过")
end
function collide(x::String, y::Int)
println("String vs Int: 类型不匹配,跳过")
end
# Julia 根据所有参数类型选择正确的方法
collide(1, 2) # Int vs Int: 数值碰撞检测
collide("a", "b") # String vs String: 文本匹配检测
collide(1, "b") # Int vs String: 类型不匹配,跳过
实际应用:形状相交检测
abstract type Shape end
struct Circle <: Shape
x::Float64
y::Float64
radius::Float64
end
struct Rectangle <: Shape
x::Float64
y::Float64
width::Float64
height::Float64
end
struct Point <: Shape
x::Float64
y::Float64
end
# 多重派发:不同形状组合的碰撞检测
function intersect(a::Circle, b::Circle)
dist = sqrt((a.x - b.x)^2 + (a.y - b.y)^2)
return dist < a.radius + b.radius
end
function intersect(a::Rectangle, b::Rectangle)
return a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y
end
function intersect(a::Point, b::Circle)
dist = sqrt((a.x - b.x)^2 + (a.y - b.y)^2)
return dist < b.radius
end
function intersect(a::Circle, b::Point)
return intersect(b, a) # 复用
end
# 使用
c1 = Circle(0.0, 0.0, 5.0)
c2 = Circle(3.0, 0.0, 3.0)
p = Point(1.0, 1.0)
intersect(c1, c2) # true
intersect(p, c1) # true
intersect(c1, p) # true(对称派发)
7. 方法定义与 Method Table
# 每个函数名有一个 method table
function process(x::Int)
println("Processing integer: $x")
end
function process(x::Float64)
println("Processing float: $x")
end
function process(x::String)
println("Processing string: $x")
end
# 查看所有方法
methods(process)
# # 3 methods for generic function "process":
# [1] process(x::Int64) in Main at REPL[1]:1
# [2] process(x::Float64) in Main at REPL[2]:1
# [3] process(x::String) in Main at REPL[3]:1
# 方法数量
n = length(methods(process)) # 3
@which 宏 — 查看派发结果
# 查看特定调用会派发到哪个方法
@which 1 + 2
# +(x::T, y::T) where T<:Union{...} in Base at int.jl:87
@which 1.0 + 2.0
# +(x::T, y::T) where T<:AbstractFloat in Base at float.jl:409
@which "hello" * "world"
# *(ss::String...) in Base at strings/basic.jl:263
@which sqrt(4.0)
# sqrt(x::Float64) in Base at math.jl:582
@methods 宏(Julia 1.9+)
# 快速查看某个函数的所有方法
@methods sqrt
8. 参数化函数
# 函数也可以有类型参数
function pick{T}(a::T, b::T)
return rand() > 0.5 ? a : b
end
pick(1, 2) # OK: T = Int64
pick(1.0, 2.0) # OK: T = Float64
# pick(1, 2.0) # ERROR: T 不匹配
# 更实用的例子
function typed_sum{T<:Number}(xs::Vector{T})
result = zero(T) # 获取类型 T 的零值
for x in xs
result += x
end
return result
end
typed_sum([1, 2, 3]) # 6 (Int64)
typed_sum([1.0, 2.0, 3.0]) # 6.0 (Float64)
9. 函数作为一等公民
# 函数可以赋值给变量
my_print = println
my_print("Hello!")
# 函数可以作为参数传递
function apply_twice(f, x)
return f(f(x))
end
apply_twice(x -> x + 1, 5) # 7
apply_twice(x -> x * 2, 3) # 12
apply_twice(sqrt, 16) # 2.0
# 函数可以作为返回值
function make_multiplier(n)
return x -> x * n
end
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10
triple(5) # 15
# 函数组合
compose(f, g) = x -> f(g(x))
sqrt_then_neg = compose(-, sqrt)
sqrt_then_neg(9.0) # -3.0
10. 内置函数速查
| 函数 | 说明 | 示例 |
|---|---|---|
length(x) | 长度/元素数 | length([1,2,3]) → 3 |
size(x) | 维度大小 | size(ones(2,3)) → (2,3) |
typeof(x) | 类型 | typeof(42) → Int64 |
isa(x, T) | 类型检查 | isa(42, Int) → true |
eltype(x) | 元素类型 | eltype([1,2]) → Int64 |
zero(T) | 零值 | zero(Float64) → 0.0 |
one(T) | 一值 | one(Float64) → 1.0 |
copy(x) | 浅拷贝 | |
deepcopy(x) | 深拷贝 | |
show(x) | 显示值 | |
print(x) | 打印 | |
println(x) | 打印+换行 | |
string(x) | 转字符串 | string(42) → “42” |
11. 业务场景
场景一:策略模式(多态)
abstract type PaymentMethod end
struct CreditCard <: PaymentMethod
number::String
cvv::String
end
struct PayPal <: PaymentMethod
email::String
end
struct Crypto <: PaymentMethod
wallet::String
end
# 多重派发实现策略模式
function charge(method::CreditCard, amount)
println("Charging \$$(amount) to credit card $(method.number[1:4])****")
end
function charge(method::PayPal, amount)
println("Charging \$$(amount) via PayPal ($(method.email))")
end
function charge(method::Crypto, amount)
println("Charging \$$(amount) to crypto wallet $(method.wallet)")
end
# 使用
cc = CreditCard("4111111111111111", "123")
pp = PayPal("[email protected]")
charge(cc, 99.99) # Charging $99.99 to credit card 4111****
charge(pp, 49.99) # Charging $49.99 via PayPal ([email protected])
场景二:单位转换系统
to_celsius(t::Float64, ::Val{:fahrenheit}) = (t - 32) * 5/9
to_celsius(t::Float64, ::Val{:celsius}) = t
to_celsius(t::Float64, ::Val{:kelvin}) = t - 273.15
from_celsius(t::Float64, ::Val{:fahrenheit}) = t * 9/5 + 32
from_celsius(t::Float64, ::Val{:celsius}) = t
from_celsius(t::Float64, ::Val{:kelvin}) = t + 273.15
convert_temp(t::Float64, from::Symbol, to::Symbol) =
from_celsius(to_celsius(t, Val(from)), Val(to))
convert_temp(212.0, :fahrenheit, :celsius) # 100.0
convert_temp(0.0, :celsius, :kelvin) # 273.15
扩展阅读
- Julia 官方文档 — Functions
- Julia 官方文档 — Methods
- Julia 官方文档 — Multiple Dispatch
- 多重派发的起源论文
- JuliaCon: The Unreasonable Effectiveness of Multiple Dispatch
📌 本章小结: Julia 函数有多种定义形式,支持匿名函数、关键字参数、可变参数和默认值。多重派发根据所有参数类型选择方法,是 Julia 最核心的特性。
@which宏可用于查看派发结果。函数是一等公民,可赋值、传递和返回。