Julia 中的函數(shù)是將一系列參數(shù)組成的元組映設(shè)到一個返回值的對象,Julia 的函數(shù)不是純的數(shù)學(xué)式函數(shù),有些函數(shù)可以改變或者影響程序的全局狀態(tài)。Julia 中定義函數(shù)的基本語法為:
function f(x,y)
x + y
end
Julia 中可以精煉地定義函數(shù)。上述傳統(tǒng)的聲明語法,等價于下列緊湊的“賦值形式”:
f(x,y) = x + y
對于賦值形式,函數(shù)體通常是單表達(dá)式,但也可以為復(fù)合表達(dá)式(詳見復(fù)合表達(dá)式)。Julia 中常見這種短小簡單的函數(shù)定義。短函數(shù)語法相對而言更方便輸入和閱讀。
使用圓括號來調(diào)用函數(shù):
julia> f(2,3)
5
沒有圓括號時, f
表達(dá)式指向的是函數(shù)對象,這個函數(shù)對象可以像值一樣被傳遞:
julia> g = f;
julia> g(2,3)
5
調(diào)用函數(shù)有兩種方法:使用特定函數(shù)名的特殊運算符語法(詳見后面函數(shù)運算符),或者使用 apply
函數(shù):
julia> apply(f,2,3)
5
apply
函數(shù)把第一個參數(shù)當(dāng)做函數(shù)對象,應(yīng)用在后面的參數(shù)上。
和變量名稱一樣, 函數(shù)名稱也可以使用 Unicode 字符:
julia> ∑(x,y) = x + y
∑ (generic function with 1 method)
Julia 函數(shù)的參數(shù)遵循 “pass-by-sharing” 的慣例,即不傳遞值,而是傳遞引用。函數(shù)參數(shù)本身,有點兒像新變量綁定(引用值的新位置),但它們引用的值與傳遞的值完全相同。對可變值(如數(shù)組)的修改,會影響其它函數(shù)。
return
關(guān)鍵字函數(shù)返回值通常是函數(shù)體中最后一個表達(dá)式的值。上一節(jié)中 f
是表達(dá)式 x + y
的值。在 C 和大部分命令式語言或函數(shù)式語言中, return
關(guān)鍵字使得函數(shù)在計算完該表達(dá)式的值后立即返回:
function g(x,y)
return x * y
x + y
end
對比下列兩個函數(shù):
f(x,y) = x + y
function g(x,y)
return x * y
x + y
end
julia> f(2,3)
5
julia> g(2,3)
6
在純線性函數(shù)體,比如 g 中,不需要使用 return
,它不會計算表達(dá)式 x + y
??梢园?x * y
作為函數(shù)的最后一個表達(dá)式,并省略 return
。只有涉及其它控制流時, return
才有用。下例計算直角三角形的斜邊長度,其中直角邊為 x 和 y,為避免溢出:
function hypot(x,y)
x = abs(x)
y = abs(y)
if x > y
r = y/x
return x*sqrt(1+r*r)
end
if y == 0
return zero(x)
end
r = x/y
return y*sqrt(1+r*r)
end
最后一行的 return
可以省略。
Julia 中,大多數(shù)運算符都是支持特定語法的函數(shù)。 &&
、 ||
等短路運算是例外,它們不是函數(shù),因為短路求值先算前面的值,再算后面的值。對于函數(shù)運算符,可以像其它函數(shù)一樣,把參數(shù)列表用圓括號括起來,作為函數(shù)運算符的參數(shù):
julia> 1 + 2 + 3
6
julia> +(1,2,3)
6
中綴形式與函數(shù)形式完全等價,事實上,前者被內(nèi)部解析為函數(shù)調(diào)用的形式??梢韵駥ζ渌瘮?shù)一樣,對 +
、 *
等運算符進行賦值、傳遞:
julia> f = +;
julia> f(1,2,3)
6
但是,這時 f
函數(shù)不支持中綴表達(dá)式。
有一些表達(dá)式調(diào)用特殊名字的運算符:
表達(dá)式 | 調(diào)用 |
---|---|
[A B C ...]
|
hcat
|
[A, B, C, ...]
|
vcat
|
[A B; C D; ...]
|
hvcat
|
A'
|
ctranspose
|
A.'
|
transpose
|
1:n
|
colon
|
A[i]
|
getindex
|
A[i]=x
|
setindex!
|
這些函數(shù)都存在于 Base.Operators
模塊中。
Julia 中函數(shù)是第一類對象,可以被賦值給變量,可以通過賦值后的變量來調(diào)用函數(shù), 還可以當(dāng)做參數(shù)和返回值,甚至可以被匿名構(gòu)造:
julia> x -> x^2 + 2x - 1
(anonymous function)
上例構(gòu)造了一個匿名函數(shù),輸入一個參數(shù) x ,返回多項式 x^2 + 2x - 1 的值。匿名函數(shù)的主要作用是把它傳遞給接受其它函數(shù)作為參數(shù)的函數(shù)。最經(jīng)典的例子是 map
函數(shù),它將函數(shù)應(yīng)用在數(shù)組的每個值上,返回結(jié)果數(shù)組:
julia> map(round, [1.2,3.5,1.7])
3-element Array{Float64,1}:
1.0
4.0
2.0
map
的第一個參數(shù)可以是非匿名函數(shù)。但是大多數(shù)情況,不存在這樣的函數(shù)時,匿名函數(shù)就可以簡單地構(gòu)造單用途的函數(shù)對象,而不需要名字:
julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Array{Int64,1}:
2
14
-2
匿名函數(shù)可以通過類似 (x,y,z)->2x+y-z
的語法接收多個參數(shù)。無參匿名函數(shù)則類似于 ()->3
。無參匿名函數(shù)可以“延遲”計算,做這個用處時,代碼被封裝進無參函數(shù),以后可以通過把它命名為 f()
來引入。
Julia 中可以通過返回多元組來模擬返回多值。但是,多元組并不需要圓括號來構(gòu)造和析構(gòu),因此造成了可以返回多值的假象。下例返回一對兒值:
julia> function foo(a,b)
a+b, a*b
end;
如果在交互式會話中調(diào)用這個函數(shù),但不將返回值賦值出去,會看到返回的是多元組:
julia> foo(2,3)
(5,6)
Julia 支持簡單的多元組“析構(gòu)”來給變量賦值:
julia> x, y = foo(2,3);
julia> x
5
julia> y
6
也可以通過 return
來返回:
function foo(a,b)
return a+b, a*b
end
這與之前定義的 foo
結(jié)果相同。
函數(shù)的參數(shù)列表如果可以為任意個數(shù),有時會非常方便。這種函數(shù)被稱為“變參”函數(shù),是“參數(shù)個數(shù)可變”的簡稱??梢栽谧詈笠粋€參數(shù)后緊跟省略號 ...
來定義變參函數(shù):
julia> bar(a,b,x...) = (a,b,x)
bar (generic function with 1 method)
變量 a
和 b
是前兩個普通的參數(shù),變量 x
是尾隨的可迭代的參數(shù)集合,其參數(shù)個數(shù)為 0
或多個:
julia> bar(1,2)
(1,2,())
julia> bar(1,2,3)
(1,2,(3,))
julia> bar(1,2,3,4)
(1,2,(3,4))
julia> bar(1,2,3,4,5,6)
(1,2,(3,4,5,6))
上述例子中, x
是傳遞給 bar
的尾隨的值多元組。
函數(shù)調(diào)用時,也可以使用 ... :
julia> x = (3,4)
(3,4)
julia> bar(1,2,x...)
(1,2,(3,4))
上例中,多元組的值完全按照變參函數(shù)的定義進行內(nèi)插,也可以不完全遵守其函數(shù)定義來調(diào)用:
julia> x = (2,3,4)
(2,3,4)
julia> bar(1,x...)
(1,2,(3,4))
julia> x = (1,2,3,4)
(1,2,3,4)
julia> bar(x...)
(1,2,(3,4))
被內(nèi)插的對象也可以不是多元組:
julia> x = [3,4]
2-element Array{Int64,1}:
3
4
julia> bar(1,2,x...)
(1,2,(3,4))
julia> x = [1,2,3,4]
4-element Array{Int64,1}:
1
2
3
4
julia> bar(x...)
(1,2,(3,4))
原函數(shù)也可以不是變參函數(shù)(大多數(shù)情況下,應(yīng)該寫成變參函數(shù)):
baz(a,b) = a + b
julia> args = [1,2]
2-element Int64 Array:
1
2
julia> baz(args...)
3
julia> args = [1,2,3]
3-element Int64 Array:
1
2
3
julia> baz(args...)
no method baz(Int64,Int64,Int64)
但如果輸入的參數(shù)個數(shù)不對,函數(shù)調(diào)用會失敗。
很多時候,函數(shù)參數(shù)都有默認(rèn)值。例如,庫函數(shù) parseint(num,base)
把字符串解析為某個進制的數(shù)。base
參數(shù)默認(rèn)為 10
。這種情形可以寫為:
function parseint(num, base=10)
###
end
這時,調(diào)用函數(shù)時,參數(shù)可以是一個或兩個。當(dāng)?shù)诙€參數(shù)未指明時,自動傳遞 10
:
julia> parseint("12",10)
12
julia> parseint("12",3)
5
julia> parseint("12")
12
可選參數(shù)很方便參數(shù)個數(shù)不同的多方法定義(詳見方法)。
有些函數(shù)的參數(shù)個數(shù)很多,或者有很多行為。很難記住如何調(diào)用這種函數(shù)。關(guān)鍵字參數(shù),允許通過參數(shù)名來區(qū)分參數(shù),便于使用、擴展這些復(fù)雜接口。
例如,函數(shù) plot
用于畫出一條線。此函數(shù)有許多可選項,控制線的類型、寬度、顏色等。如果它接收關(guān)鍵字參數(shù),當(dāng)我們要指明線的寬度時,可以調(diào)用 plot(x, y, width=2)
之類的形式。這樣的調(diào)用方法給參數(shù)添加了標(biāo)簽,便于閱讀;也可以按任何順序傳遞部分參數(shù)。
使用關(guān)鍵字參數(shù)的函數(shù),在函數(shù)簽名中使用分號來定義:
function plot(x, y; style="solid", width=1, color="black")
###
end
額外的關(guān)鍵字參數(shù),可以像變參函數(shù)中一樣,使用 ...
來匹配:
function f(x; y=0, args...)
###
end
在函數(shù) f
內(nèi)部,args
可以是 (key,value)
多元組的集合,其中 key
是符號。可以在函數(shù)調(diào)用時使用分號來傳遞這個集合, 如 f(x, z=1; args...).
這種情況下也可以使用字典。
關(guān)鍵字參數(shù)的默認(rèn)值僅在必要的時候從左至右地被求值(當(dāng)對應(yīng)的關(guān)鍵字參數(shù)沒有被傳遞),所以默認(rèn)的(關(guān)鍵字參數(shù)的)表達(dá)式可以調(diào)用在它之前的關(guān)鍵字參數(shù)。
可選參數(shù)和關(guān)鍵字參數(shù)的區(qū)別在于它們的默認(rèn)值是怎樣被求值的。當(dāng)可選的參數(shù)被求值時,只有在它之前的的參數(shù)在作用域之內(nèi); 與之相對的, 當(dāng)關(guān)鍵字參數(shù)的默認(rèn)值被計算時, 所有的參數(shù)都是在作用域之內(nèi)。比如,定義函數(shù):
function f(x, a=b, b=1)
###
end
在 a=b
中的 b
指的是該函數(shù)的作用域之外的 b
,而不是接下來 的參數(shù) b
。然而,如果 a
和 b
都是關(guān)鍵字參數(shù),那么它們都將在 生成在同一個作用域上,a=b
中的 b
指向的是接下來的參數(shù) b
(遮蔽了任何外層空間的 b
), 并且 a=b
會得到未定義變量的錯誤
(因為默認(rèn) 參數(shù)的表達(dá)式是自左而右的求值的,b
并沒有被賦值)。
將函數(shù)作為參數(shù)傳遞給其它函數(shù),當(dāng)行數(shù)較多時,有時不太方便。下例在多行函數(shù)中調(diào)用 map
:
map(x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A, B, C])
Julia 提供了保留字 do
來重寫這種代碼,使之更清晰:
map([A, B, C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
do x
的語法創(chuàng)建一個含有參數(shù) x
的匿名函數(shù),并將其傳給 map
作為第一個參數(shù)。類似地,do a,b
將創(chuàng)建一個含有兩個參數(shù)的匿名函數(shù),和一個樸素的 do
的聲明以 () -> ....
方式說明如下是一個匿名函數(shù)。
如何將這些參數(shù)初始化取決于“外部”函數(shù);在這里,map
將依次設(shè)置 x
到 A
, B
, C
,每個都將調(diào)用匿名函數(shù),就像在語法 map(func, [A, B, C])
中做的一樣。
因為語法的調(diào)用看起來像正常的代碼塊,所以這種語法使它更容易使用函數(shù)來有效地擴展語言。這里有許多可能完全不同于 map
的用途,如管理系統(tǒng)狀態(tài)。例如,有一個版本的 open
,運行代碼來確保打開的文件最終關(guān)閉:
open("outfile", "w") do io
write(io, data)
end
它可以通過以下定義來實現(xiàn):
function open(f::Function, args...)
io = open(args...)
try
f(io)
finally
close(io)
end
end
對比的 map
的例子,這里的 IO 是通過 open("outfile", "w")
來實現(xiàn)初始化的。字符流之后會傳遞給您的執(zhí)行寫入的匿名函數(shù);最后,open
的功能確保流在您的函數(shù)結(jié)束后是關(guān)閉狀態(tài)的。 try/finally
的構(gòu)造將在 控制流 中被描述。
do 塊語法的使用有助于檢查文檔或?qū)崿F(xiàn)了解用戶函數(shù)的參數(shù)是如何被初始化的。
更多建議: