现在位置: 首页 > Julia 教程 > 正文

Julia 函数

函数是一组一起执行一个任务的语句。

在 Julia 里,函数是将参数值组成的元组映射到返回值的一个对象。


定义函数

Julia 中使用 function 定义函数,基本语法是:

function functionname(args)
   expression
   ...
end

默认情况下,函数返回的值是最后计算的表达式的值,所以上面的写法没有 return 语句。当然,如果使用 return 关键字,函数就会立即返回:

实例

julia> function f(x, y)
           x + y
       end
f (generic function with 1 method)

julia> f(2, 3)
5

# 使用 return 提前返回
julia> function bills(money)
           if money < 0
               return false
           else
               return true
           end
       end
bills (generic function with 1 method)

julia> bills(50)
true

julia> bills(-50)
false

简写形式

当函数体只有一个表达式时,可以省略 function 关键字,用赋值形式定义函数,这是 Julia 中非常常用的风格:

实例

julia> f(x, y) = x + y
f (generic function with 1 method)

julia> f(2, 3)
5

julia> func(x, y) = sqrt(x^2 + y^2)
func (generic function with 1 method)

julia> func(5, 4)
6.4031242374328485

没有括号时,表达式 f 指的是函数对象,可以像任何值一样被传递:

实例

julia> g = f;

julia> g(2, 3)
5

和变量名一样,Unicode 字符也可以用作函数名:

实例

julia> ∑(x, y) = x + y
∑ (generic function with 1 method)

julia> ∑(2, 3)
5

参数类型声明

可以在参数名后用 :: 声明参数类型,用来限定函数适用的类型范围:

实例

# 只接受整数类型的参数
julia> fib(n::Integer) = n <= 2 ? 1 : fib(n-1) + fib(n-2)
fib (generic function with 1 method)

julia> fib(10)
55

julia> fib(1.5)
ERROR: MethodError: no method matching fib(::Float64)

建议:给参数声明类型不会提高性能(Julia 会为实际传入的类型自动编译特化版本),它的作用主要是限制派发、保证正确性和增强可读性。不确定时可以不加类型声明,或使用更宽泛的抽象类型如 Integer、Real。


返回类型与多返回值

我们可以使用 :: 运算符在函数签名后指定返回类型:

实例

julia> function g(x, y)::Int8
           return x * y
       end;

julia> typeof(g(1, 2))
Int8

以上函数实例会把返回值转换为 Int8 类型。需要注意返回类型声明在 Julia 中较少使用,更好的做法是写出类型稳定的函数。

如果函数要返回多个值,可以使用元组,调用时用解构接收:

实例

julia> function mul(x, y)
           x + y, x * y
       end
mul (generic function with 1 method)

julia> mul(5, 10)
(15, 50)

# 解构赋值接收多个返回值
julia> s, p = mul(5, 10)
(15, 50)

julia> s
15

约定:按惯例,只产生副作用不返回有意义结果的函数应显式返回 nothing。


可选参数

在函数中我们可以设置参数默认值,这样在没有提供该参数的时候,就可以使用默认值来计算:

实例

julia> function pos(ax, by, cz=0)
           println("$ax, $by, $cz")
       end
pos (generic function with 2 methods)

julia> pos(10, 30)
10, 30, 0

julia> pos(10, 30, 50)
10, 30, 50

关键字参数

Julia 关键字参数允许通过名称而不是仅通过位置来识别参数,使得这些复杂函数易于使用和扩展。

使用关键字参数,需要在函数的位置参数之后使用分号 ;,并在其后跟一个或多个键值对 key=value:

实例

julia> function foo(a, b; c=10, d="hi")
           println("a is $a")
           println("b is $b")
           return "c => $c, d => $d"
       end
foo (generic function with 1 method)

julia> foo(100, 20)
a is 100
b is 20
"c => 10, d => hi"

julia> foo("Hello", "Runoob", c=pi, d=22//7)
a is Hello
b is Runoob
"c => π, d => 22//7"

重要:调用时位置参数必须写在关键字参数之前,关键字参数之间可以任意排列顺序,例如 foo("Hello", "Runoob", d=22//7, c=pi) 也是正确的。但 foo(c=pi, "Hello", "Runoob") 这种关键字在前、位置在后的写法是错误的,会直接报 MethodError。

没有被匹配到的关键字参数可以用 kwargs... 收集起来,它是一个具名元组:

实例

julia> function f(x; y=0, kwargs...)
           println("x = $x, y = $y")
           println("其他关键字: $kwargs")
       end
f (generic function with 1 method)

julia> f(1; a=2, b=3)
x = 1, y = 0
其他关键字: (a = 2, b = 3)

可变参数函数(Varargs)

在最后一个位置参数后面加上 ...,可以定义接受任意数量参数的函数,多余的参数会被收集成一个元组:

实例

julia> bar(a, b, x...) = (a, b, x)
bar (generic function with 1 method)

julia> bar(1, 2)
(1, 2, ())

julia> bar(1, 2, 3, 4)
(1, 2, (3, 4))

反过来,在调用函数时用 ... 可以把一个集合"拆开"(splatting)作为单独的参数传入:

实例

julia> x = (3, 4)
(3, 4)

julia> bar(1, 2, x...)
(1, 2, (3, 4))

julia> max([1, 5, 3]...)
5

匿名函数

匿名函数是一个没有函数名的函数,语法使用符号 ->:

实例

julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)

julia> function (x)
           x^2 + 2x - 1
       end
#3 (generic function with 1 method)

匿名函数最主要的用法是传递给接收函数作为参数的函数。一个经典的例子是 map,为数组的每个元素应用一次函数,然后返回一个包含结果值的新数组:

实例

julia> map(round, [1.2, 3.5, 1.7])
3-element Vector{Float64}:
 1.0
 4.0
 2.0

julia> map(x -> x^2 + 2x - 1, [1, 3, -1])
3-element Vector{Int64}:
  2
 14
 -2

接受多个参数的匿名函数写法可以使用语法 (x,y,z) -> 2x+y-z,而无参匿名函数写作 ()->3。

do 语句块

当需要向函数传入多行的匿名函数时,可以使用 do 语句块,让代码更像普通的代码块:

实例

# 以下两种写法完全等价
julia> map(x -> begin
           if x < 0
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end, [-2, 0, 3])
3-element Vector{Int64}:
 0
 1
 3

# 使用 do 块更清晰
julia> map([-2, 0, 3]) do x
           if x < 0
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end
3-element Vector{Int64}:
 0
 1
 3

do x 语法会创建一个以 x 为参数的匿名函数,并把它作为第一个参数传给外层函数。

do 语句块最常见的用途是资源管理,例如 open 函数确保文件最终一定会被关闭:

实例

open("outfile.txt", "w") do io
    write(io, "RUNOOB")
end

它的实现原理大致如下:open 先打开文件,用 try/finally 保证无论函数是否抛出异常,文件都会被 close:

function open(f::Function, args...)
    io = open(args...)
    try
        f(io)
    finally
        close(io)
    end
end

函数组合与管道

Julia 提供了两个方便的运算符来组合函数。

∘(输入 \circ 后按 Tab)是函数组合运算符,(f ∘ g)(x) 等价于 f(g(x)):

实例

julia> (sqrt ∘ +)(3, 6)
3.0

julia> map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
6-element Vector{Char}:
 'U': ASCII/Unicode U+0055 (category Lu: Letter, uppercase)
 'N': ASCII/Unicode U+004E (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

|> 是管道运算符,把左边的值作为参数传给右边的函数,让数据处理的流向更加直观:

实例

# 1:10 |> sum |> sqrt 等价于 sqrt(sum(1:10)) julia> 1:10 |> sum |> sqrt 7.416198487095663 [/mycode4]

点语法向量化

任何函数 f 都可以通过 f.(A) 的语法逐元素地应用到数组上,等价于 broadcast(f, A):

实例

julia> A = [1.0, 2.0, 3.0];

julia> sin.(A)
3-element Vector{Float64}:
 0.8414709848078965
 0.9092974268256817
 0.1411200080598672

# 多参数函数也可以广播
julia> f(x, y) = 3x + 4y
f (generic function with 1 method)

julia> f.(A, [10, 20, 30])
3-element Vector{Float64}:
 43.0
 86.0
129.0

嵌套的点调用会自动融合成单次循环,X .= f.(Y) 还可以把结果原地写入 X,详细内容见数组章节的广播部分。


Map 与 Filter

map(func, coll) 把函数依次应用于集合 coll 的每个元素,返回一个新的集合:

实例

julia> map(A -> A^3 + 3A - 3, [10, 3, -2])
3-element Vector{Int64}:
 1027
   33
  -17

filter(function, collection) 返回集合的副本,只保留让函数返回 true 的元素:

实例

julia> array = [1, 2, 3, 4, 5, 6];

julia> filter(x -> x % 2 == 0, array)
3-element Vector{Int64}:
 2
 4
 6

函数嵌套与递归

在 Julia 中,函数可以嵌套使用。内层函数可以访问外层函数的变量:

实例

julia> function add(x)
           y = x * 2
           function add1(y)
               return y + 1
           end
           return add1(y)
       end
add (generic function with 1 method)

julia> add(10)
21

同样,Julia 中的函数也可以是递归的。递归指的是在函数的定义中使用函数自身的方法。

以下我们使用三元运算符来写递归,三元运算符 expr ? a : b,如果 expr 为 true,值为 a 的计算结果,否则为 b 的计算结果:

实例

julia> mysum(x) = x > 1 ? mysum(x-1) + x : x
mysum (generic function with 1 method)

julia> mysum(10)
55

递归最著名的例子是计算第 n 个斐波那契数:

实例

julia> fib(x) = x < 2 ? x : fib(x-1) + fib(x-2)
fib (generic function with 1 method)

julia> fib(10)
55

julia> fib(20)
6765

提示:直接递归的 fib 计算到 fib(50) 左右就会非常慢(指数级复杂度)。实际使用中可以用字典缓存中间结果,或改用迭代实现。