上一章说到了 Mojo 中函数的参数类型和参数传递方式,这一章继续来说一下函数中参数相关的其他内容。
可选参数
有一个缺省值的参数,就可以作为可选参数,在调用时无需指定。
fn pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn main():
# Uses the default value for `exp`
var z = pow(3)
print(z)
输出:
$ mojo 7-1.mojo
9
因为可选参数在调用时可以不指定,因此不能将它设为 inout 传参方式。
fn pow(base: Int, inout exp: Int = 2) -> Int:
return base ** exp
$ mojo 7-1.mojo
/home/mojotest/7-1.mojo:1:34: error: inout arguments may not have defaults
fn pow(base: Int, inout exp: Int = 2) -> Int:
^ ~
关键字参数
当调用某个函数时,可以利用关键字参数(也就是形参名称作为关键字)来传递参数。如果使用关键字参数,就可以以任意的顺序传递。
fn pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn main():
# Uses the default value for `exp`
var z = pow(exp=3, base=2)
print(z)
$ mojo 7-2.mojo
8
可变参数
可变参数指的是可以让一个函数接受数目不定的参数,这可以通过 *argument_name 这样的语法来指定。
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum+value
return sum
fn main():
print(sum(1, 2, 3, 4))
print(sum(20, 30))
$ mojo 7-3.mojo
10
50
目前,所有的可变参数必须是同一类型的。但是不同类型的可变参数也是有需求的,例如在print() 里面,需要打印的参数类型就是不一样的,这个在标准库实现的时候利用到了一些未公开的MLIR API。因此,虽然目前对外还不支持,未来 Mojo 是计划要支持的。
函数内部所得到的可变参数是一个可以迭代的列表(List),这样用 for …in 的循环就可以很方便的使用。但这里根据参数的类型,在使用上还有些区别。假如类型是Int这样从寄存器就可以传递的类型,直接可以用这个值了,如上例中的 value 。但如果类型是String这样是在内存中的,目前在迭代中获得的值实际上是一个引用,因此需要加上一个空的下标操作符[]才能解引用,获得真正的值。
fn make_worldly(inout *strs: String):
for i in strs:
# Requires extra [] to dereference the reference for now.
i[] += " world"
还有一种方法就是直接利用列表的下标来获得具体的值,也即:
fn make_worldly(inout *strs: String):
for i in range(len(strs)):
strs[i] += " world"
可变关键字参数
可变关键字参数和可变参数类似,都可以让用户传递数目不定的参数,只是可变关键字参数是通过传递一组关键字参数(keyword argument),而非位置参数(positional argument),这是通过语法 **kwargs 来指定的。
fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
def main():
print_nicely(a=7, y=8)
$ mojo 7-4.mojo
a = 7
y = 8
函数内部所得到的可变参数是一个可以迭代的字典(Dict)。这同样有一些限制:
- 所有的关键词参数,目前都只能是同一类型。
- 所有的关键词参数都是 owned 的,也就是按值传递的,不能够被定义为其它的。
- 尚不支持字典的解包(unpack)
fn takes_dict(d: Dict[String, Int]) :
put(**d) # not supported yet
在Python中一个常见的写法如下,Mojo 中也是可以的:
def powerful(a, b, *args, **kwargs):
...
仅位置参数或者仅关键字参数
有时候我们希望某些参数只能是作为位置参数传递,或者只能作为关键字参数传递。
如果要定义仅位置参数,可以在参数列表中加入 / ,前面的参数就是仅能作为位置参数传递了。
fn test(a: Int, b: Int, /, c: Int=10):
print("a:", a, "b:", b, "c:", c)
def main():
# 这些写法都是可以的
test(1, 2)
test(1, 2, 3)
test(1, 2, c=3)
# 但下面的写法会出错
# test(a=1, 2, 3)
# test(a=1, b=2, 3)
$ mojo 7-5.mojo
a: 1 b: 2 c: 10
a: 1 b: 2 c: 3
a: 1 b: 2 c: 3
如果要定义仅关键字参数,可以在参数列表中加入 *,后面的参数就是仅能作为关键字参数传递
fn test(a: Int, b: Int, *, c: Int=10):
print("a:", a, "b:", b, "c:", c)
def main():
# 这些写法都是可以的
test(1, 2)
test(1, 2, c=3)
test(1, b=2, c=3)
test(a=1, b=2, c=3)
# 但下面的写法会出错, c 只能作为关键字参数传递
# test(1, 2, 3)
$ mojo 7-6.mojo
a: 1 b: 2 c: 10
a: 1 b: 2 c: 3
a: 1 b: 2 c: 3
a: 1 b: 2 c: 3
还有一个特殊情况,如果函数接受可变参数,那么任何在可变参数之后的定义的参数也将被视为仅关键字参数(否则,这些参数如果不使用关键字指定的话,就会被当做可变参数的一部分了,即使是类型和可变参数指定的类型不同)
fn print_args(*values: Int, sep: String = ' '):
for v in values:
print(v, end='')
print(sep, end='')
print('')
fn main() :
print_args(1, 2, 3, 4)
print_args(1, 2, 3, 4, sep='|')
# 下面这句就是错误的,sep只能通过关键字参数方式传递
#print_args(1, 2, 3, 4, ',')
$ mojo 7-7.mojo
1 2 3 4
1|2|3|4
重载函数
重载函数(Overloaded function)是编程语言中的一个常见概念。同名的函数如果有不同的参数列表就叫重载函数,调用方会根据实参的类型来选择合适的函数进行调用。
在Python中,因为 def 的参数没有指定类型,因此可以接受任意类型的参数。(虽然Python里也可以写def test(a:int, b:str) 这样给参数指定类型提示(Type Hints),但是其只起到提示的作用,其实并不会进行类型检查)这样在Python中无需重载函数,一般做法都是在函数内部判断参数类型,再走不通的分支。
在Mojo中,def 如果不指定参数类型,那么还是一样的逻辑。但是 fn 需要指定参数类型,因此如果类型不一致的时候,你就需要分开实现针对不同参数类型的函数。例如:
fn add(x: Int, y: Int) -> Int:
return x + y
fn add(x: String, y: String) -> String:
return x + y
如果调用 add() 的时候给出的参数不是 Int 或者 String 类型的,那么就会报错,除非参数类型可以隐式转换成 Int 或者 String 类型。
在这种情况下,其实也可以再定义一个 def 函数,作为兜底(fallback),当所有的 fn 都选择不上时,可以选择用 def函数。