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'