强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 函数有多种定义形式,支持匿名函数、关键字参数、可变参数和默认值。多重派发根据所有参数类型选择方法,是 Julia 最核心的特性。@which 宏可用于查看派发结果。函数是一等公民,可赋值、传递和返回。