BOO大全/函式
函式
编辑Boo 的函式以 def 關鍵字定義,而回傳值則以 return 表示傳回。
def sqr(x as double):
return x*x
與 Python 最大的不同在於通常都要指定引數的型別,這點很重要,通常這被用來認定是否為靜態型別(也有效率)語言,下面是以 C# 所寫的代碼,目的與上面一樣。(譯註:引數型別可以不用指定)
double sqr(double x) {
return x*x;
}
正如變數型別可以由初始運算式推導而出一樣,函式的回傳型別通常也可以由回傳值運算式的型別推導而得。(例外狀況可以參考:遞迴函式)
部份語言允許引數也可推導而出,但 Boo 需要指定引數型別。如果你不指定,那麼型別隱含地會是 Object,在後續處理上,你可能需要作必要的轉型。指定引數型別之所以好,是因為你可以很清楚地明白引數的型別,也可以在多型的情況下,很清楚的明白是使用了哪一個函式。強型別語言本身已經說明了很多事情,在動態語言裡,往往很多事情都需要放在程式註解裡。
不像是 C# 或 Java,Boo 函式可以獨立宣告,也不一定要定義在變數宣告之後。這應該會讓你很驚訝,因為這幾乎是一個全然自由的 Python 環境了。(不過,Boo並不是Python)
引數傳遞到函式時,是傳遞值,但是記住,當處理物件時,是傳遞參考。(參考與物件).
>>> res (20, 2.4) >>> def Fun(x as (double)): ... x[0] = 1.0 ... >>> Fun(res) >>> res (1, 2.4)
函式也是一個物件,所以你可以將他們指派給變數 (類似 C 的函式指標)。其型別為 CallableTypes。
def yes():
print "yes was entered"
def no():
print "no was entered"
line = prompt("enter yes or no:")
if line == "yes":
fn = yes
else:
fn = no
fn()
遞迴函式
编辑Boo 的遞迴函式比較特別的是,他們通常需要明確地指定回傳型別,因為這很難由型別推導中得到。下面是一個很經典的階乘遞迴函式:
def fact(n as int) as int:
if n < 2:
return 1
else:
return n*fact(n-1)
fact 並不是個很有用或很有效率的函式,但它卻很適合用來表達我想說的。
下面這個或許比較有用。目錄樹很自然地就是遞迴結構,目錄包含目錄...等。FilesInFolder 函式回傳一個包含目錄下所有符合特定樣式檔案的串列。首先它建立一個串列用以包含目錄下所有檔案,然後再繼續訪問其下的目錄,把檔案加入串列。到最後沒有檔案可加了,就會終止。
import System.IO
def FilesInFolder(path as string, mask as string) as List:
files = [file for file in Directory.GetFiles(path,mask)]
for dir in Directory.GetDirectories(path):
files.Extend(FilesInFolder(dir,mask))
return files
res = FilesInFolder(Directory.GetCurrentDirectory(),"*.boo")
print join(res,'\n')
下面函式 Reverse 只能運作在 List 上,它以遞迴的方式將串列的順序顛倒﹔ ls[0:1] 是個新的串列,裡面包含 ls 的第一個元素,而 ls[1:] 則包含剩餘的元素。 (在要複製串列時,建議使用 ls[:] 進行。)
def Reverse(ls as List) as List:
if len(ls) < 2:
return ls[:]
else:
return Reverse(ls[1:])+ls[0:1]
Boo並不是Python
编辑Boo 使用 Python 的語法,同時加入許多很酷的 Python 特性,像字串與陣列的slicing、串列...等等。但是它是個非常不同的語言,就像 Java 明顯地是以C++語法為基礎但卻不是 C++ 一樣。所以語法本身並不重要,它只是語言的表面。Python 是個動態型別的語言,Boo 則全然是個靜態型別的語言 (但是,請參考 DuckTyping,只是在很多地方使用了型別推導來避免明確地指定型別。
函式多載
编辑很多時候,函數會有相同的意思,但卻針對不同的型別提供不同的實作。舉例來說,System.Max就針對各種數值型別提供了不同的實作。這裡Square函數就有兩個多載:
def Square(n as int):
return n*n
def Square(x as double):
return x*x
assert Square(10).GetType() == int
assert Square(2.3).GetType() == double
雖然不為 int 或其他數執行別進行多載,也可以運行,但是結果卻會是 double。在當我們真的需要傳回值型別與引數型別相同時,進行多載就非常的重要了。不過,這樣看來很多餘,甚至寫習慣 Python 的人也不覺得有什麼。(這樣的多載其實可以使用泛型(Generic)來作,但是我們得等等)
有一個引數以上的函式,一般對額外的引數都會提供預設值。Boo 無法只傳部份引數,所以多載是一個讓函式使用上更友善的方法。
def DrawBox(pos as Point, size as Size, colour as Color):
....
def DrawBox(pos as Point, size as Size):
DrawBox(pos,size,Color.Black)
def DrawBox(pos as Point):
DrawBox(pos,Size(1,1),Color.Black)
與這很接近的,Boo 可以讓你在建構物件時就指定公開屬性的值,例如:btn = Button(Text : "Hello", Location : Position(100,100))
根據守則,如果方法的多載有相同數目的引數時,引數的型別應該盡可能地清楚。如果沒指定型別,可能會造成 Boo 不知道該呼叫哪一個。
>>> def f(x as single): ... return x ... >>> def f(i as int): ... return i ... >>> f(2.3) -----^ ERROR: Ambiguous reference 'f': Input43Module.f(System.Single), Input44Module.f(System.Int32).
如果多載函式的引數是數值型別的話,一定要加上 double 的版本,因為編譯器可以輕易地在 double 與其他數值型別間作轉換。
傳遞多個值
编辑函式可以接收不定個數的引數。下面的函式就可以接受不定個數的浮點數。你可以看到,它使用 '*' 運算子將引數指定為陣列。你也可以傳遞浮點數陣列給 Max,但要記得加上展開運算子 '*':
>>> def Max(*x as (double)): ... ret = double.MinValue ... for val in x: ... ret = System.Math.Max(ret,val) ... return ret ... >>> Max(2.4,2,4,5,4) 5 >>> arr = (1.0,5,6,4,2) >>> Max(*arr) 6
在函式裡面,引數就是被當作陣列。事實上,這也說明了這個特性是怎麼被實作出來的,它就跟 Max( (2.0, 4.3, 2, 0) ) 一樣。
函式當然也可以有其他正常的引數,不過他們必須被放在前面:
import System
def PrintNumbers(s as string, *numbers as (double)):
Console.Write("{0}: ",s)
for x in numbers:
Console.Write("{0} ",x)
Console.WriteLine()
PrintNumbers("Some numbers",2,3,6,2,1)
回傳多個值
编辑有兩種方法可以讓你從 Boo 函式裡傳出多個值。第一種方法是使用參考引數:
def Modifies(ref a as int, ref x as double):
a = 20
x = 2.4
k = 1
z = 1.2
Modifies(k,z)
print k,z
輸出結果
20 2.4
這與 C# 的 ref 一樣﹔但在傳遞引數時,不需要使用 ref(譯註:C# 要傳引數到進去時,需明確指定 ref,例如:Method(ref val); )。這意味著除非看代碼,否則你無法知道哪個變數被函式改變了。這很糟,所以使用這個特性時要小心。
另外一種方法是以陣列傳回。
>>> def ReturnsTwo(): ... return (20,2.4) ... >>> res = ReturnsTwo() >>> res (20, 2.4) >>> x1,x2 = ReturnsTwo() >>> print x1,x2 20 2.4
我們可以看到,陣列可以被解封為變數,這讓語法看來十分地優雅。如果陣列裡的元素型別不同的話,會是一個 object 型別的陣列,解封也仍然可以運作。
>>> def ReturnsAMixedBag(): ... return ("a string",20,2.4) ... >>> s,i1,x1 = ReturnsAMixedBag()
匿名函式
编辑匿名函式一般被稱作 closures,有兩個好處:
- 你不用取名字
- closures 包含當前的狀態
在 Boo 裡的重要性遠比 Python 來的高,因為在 Boo 裡並不是區域的 Nested function。
讓我們拿前面的例子作點修改:
line = prompt("enter yes or no:")
if line == "yes":
fn = def():
print "yes was entered"
else:
fn = def():
print "no was entered"
fn()
closures 跟一般函數一樣也可以有引數:
if argv[0] == "sqr":
fn = def(x as double):
return x*x
elif argv[0] == "cube":
fn = def(x as double):
return x*x*x
也可以用來作為某個函數的快捷用法:
getline = def():
return System.Console.In.ReadLine()
while line = getline():
print line
另一種用法是使用大括號 { }。這裡隱藏了一個 return 述句,不過你應該很輕易就能看出他的語意。
getline = { System.Console.In.ReadLine() }
while line = getline():
print line
map 內建函式可以將序列裡的每個元素傳入指定的函式來進行處理。下面就是將序列裡每個元素都作平方的例子:
def sqr(x as double):
return x*x
iter = map(range(0,10),sqr)
print join(iter)
你可以寫的更短:
iter = map(range(0,10)) def(x as double):
return x*x
for n in iter:
print n
或者乾脆用大括號 { }
iter = map(range(0,10),{x as double | x*x})
for n in iter:
print n
該用哪種語法呢?雖然你可以把超過一行的述句放到 { } (通常被稱作行內closure)裡面,但它並不是個好的用法。這個語法只適用於傳遞值的簡單函式,這種情況下夠清楚。但遇到較為複雜的動作時,使用區塊的語法會比較好。
closure 可以存取範圍外的任何變數。
list = [1,2,3,4,5]
sum = 0.0
l2 = map(list) def(i as int):
sum += i
return sum
print join(l2)
輸出結果
1 3 6 10 15
callable型別
编辑前面我們提到可以將函式指派給變數。但這是什麼型別呢?我們可能會需要在函式宣告用到這型別的正確名稱,事實上,的確我們會很常會要把函式當作一個引數傳給另一個函式。
callable DoubleFun(x as double) as double
def Dump(fn as DoubleFun):
for i in range(0,10):
print i,fn(i)
Dump(System.Math.Sin)
關鍵字callable建立一個新的 callable 型別,之後則是他的命名。這與 C 使用 typedef 定義函式指標的作用非常類似,但不一樣的是 callable 建立新型別,而不是別名。Boo 如果在遇到型別相近的情況時,會自動作適當的轉換。下面的例子演示了引數為整數的函式也能傳入 Dump 裡面。
def isqr(i as int):
return i*i
Dump(isqr)
試著傳遞另一個宣告不同的函式,將會得到編譯時期錯誤:
def dbl(s as string):
return s+s
Dump(dbl)
*** Dump(DoubleFun) is not compatible with the argument list
*** '(callable(System.String) as System.String)'
在遇到某些不知道函式宣告的情況,事實上 callable 相當寬容,舉例來說,當寫作 Windows forms 程式時,可以使用closure作為按鍵的處理動作:
btn.Click += def(o as object, e as EventArgs):
print "I was clicked"
但很多情況下,宣告了引數卻不一定會使用到,所以其實可以省略,Boo允許你這樣寫:
btn.Click += def():
print "That's better!"
ICallable介面
编辑通常 Boo 允許你呼叫 callable型別,不過你也可以讓你的類別可以被當作函式呼叫。你只要讓你的類別實作ICallable介面即可:
class Fred(ICallable):
def Call(args as (object)) as object:
return (args[0] as string) + (args[1] as string)
f = Fred()
assert f('one','two') == 'onetwo'