Nim 里管函数/方法叫过程(procedure)。
定义过程
定义一个过程,以 proc 关键字开始。比如:
# 简单过程proc add(i, j: int): int =return i + j# 太过简单的过程proc multiply(i, j: int): int = i * j# 递归过程proc fibonacci(i: int) : int =if i <= 1: return ireturn fibonacci(i - 1) + fibonacci(i - 2)# 无返回值的过程proc greeting(name: string): void =echo "Hello, ", name# 无参数的过程,同时省略了 :voidproc printSeparator() =echo "--------"
过程的使用
在使用过程时,参数值可以写到括号里,也可以不写到括号里。有时,为了防止歧义,参数值一定要写到括号里。比如:
greeting("Tom") # 正常写法greeting "Tom" # greeting 后面的值被作为参数传入echo add(1, 2) # 1 和 2 作为 add 的两个参数传入 add 过程# add 的返回值被作为 echo 的参数传入 echo 过程echo add, 1, 2 # 歧义,出现编译时错误
参数
默认值
过程参数中的参数可以有个默认值:
proc doSth(check: bool = true) =if check: echo "checked"else: echo "not checked"doSth()doSth false
参数的可变性
参数变量通常是不可变的:
proc add2(i, j: int): int =i = i + j # 编译时错误,i 不可以被再赋值return i
但这并不是绝对的,可以通过把变量声明为可变的(加个 var)来改变这一点:
proc divmod(a, b: int; res, remainder: var int) =res = a div bremainder = a mod bvar x, y: intdivmod(8, 5, x, y) # modifies x and yecho x # 1echo y # 3
命名参数
如果一个过程有很多个参数,光搞清楚这些参数的顺序就够头疼了。这时,可以用命名参数来拯救我们。假如有这样一个过程:
proc createWindow(x, y, width, height: int; title: string;show: bool): Window =# implementation ommitted
下面两个过程调用等价,但是第一个看起来就让人很懵逼:
var w = createWindow(0, 0, 600, 400, "My Application", true)var w = createWindow(show = true, title = "My Application",x = 0, y = 0, height = 600, width = 800)
我们还可以把参数顺序和命名参数混合起来使用。下面的调用和上面两个等价:
var w = createWindow(0, 0, title = "My Application",height = 600, width = 800, true)
varargs
可以使用 varargs 来使过程接受很多个同类型的变量:
proc sum(nums: varargs[int]): int =var result = 0for n in nums:result += nreturn resultecho sum() # 输出 0echo sum(1) # 输出 1echo sum(1, 2, 3) # 输出 6
返回值
result 变量
在 Nim 的过程中,有一个隐式的 result 变量来表示过程的返回值。这个 result 变量使用默认值进行初始化。如果在过程求值过程中,使用了这个隐式的 result 变量,那么在返回计算结果时,可以用 return 来替代 return result:
proc sumTillNegative(x: varargs[int]): int =for i in x:if i < 0:returnresult = result + i
由于 result 是隐式的,所以如果你在过程中显式声明了 result 变量的话,这个隐式的 result 就不起作用了。
可丢弃返回值
函数的返回值是不可以直接丢掉的。如果要丢掉,可以使用 discard 语句:
proc combine(i, j: int): int =echo "combined two integers"return i + jcombine(1, 2) # 不可以discard combine(1, 2) # 可以
如果想要优雅地丢弃返回值,可以把返回值声明为可丢弃的:
proc combine(i, j: int): int {.discardable.} =echo "combined two integers"return i + jcombine(1, 2) # 可以
过程重载
Nim 支持 C++ 那样的过程重载:
proc toString(x: int): string =result =if x < 0: "negative"elif x > 0: "positive"else: "zero"proc toString(x: bool): string =result =if x: "yep"else: "nope"assert toString(13) == "positive" # calls the toString(x: int) procassert toString(true) == "yep" # calls the toString(x: bool) proc
运算符/操作符
运算符本质上就是一个拥有特殊名称(名称是操作符)的过程。Nim 中操作符分为两类:
- 中缀操作符,比如
+(a+b)、*(a*b)、<(a<b) 等 - 前缀操作符,比如
+(+a)等
除了內建的关键字 and、or、not 之外,操作符通常包含这些字符:\、/、<、>、=、@、$、~、&、%、!、?、^、.、|。没有什么可以阻止你用上面的字符来定义自己的运算符,但是如果不恰当地使用,通常会影响代码可读性。
定义操作符的方式和定义普通过程差别不大,不过需要用操作符作为过程名,同时要把操作符用 ``` 括起来:
proc `$` (x: myDataType): string = ...# 现在 $ 操作符应用到 myDataType 类型的变量上了
我们甚至可以使用 ``` 来把操作符像普通过程那样使用:
if `==`( `+`(3, 4), 7): echo "True"
前置声明
和 C/C++ 一样,在 Nim 中要使用一个名词,需要先声明出来。比如:
proc sum(i, j, k: int): int = add(i, add(j, k)) # 不行,add 没有在 sum 之前声明proc add(i, j: int): int = i + j
要使这段代码正常编译,有一下两种方式改造:
# 方式一:把 add 定义到 sum 前面proc add(i, j: int): int = i + jproc sum(i, j, k: int): int = add(i, add(j, k))# 方式二:先把 add 声明出来proc add(i, j: int): int # 先声明有这样一个 add 过程proc sum(i, j, k: int): int = add(i, add(j, k))proc add(i, j: int): int = i + j
对于这个简单的例子,使用方式一其实就够了。但是对于某些情况,比如递归调用,就只能用方式二:
# 先把 even 过程声明出来proc even(n: int): boolproc odd(n: int): bool =assert(n >= 0) # makes sure we don't run into negative recursionif n == 0: falseelse:n == 1 or even(n-1)proc even(n: int): bool =assert(n >= 0) # makes sure we don't run into negative recursionif n == 1: falseelse:n == 0 or odd(n-1)
这种预先把过程声明出来,而在后面给出具体实现的方式就是声明前缀。
