Nim 里管函数/方法叫过程(procedure)。

定义过程

定义一个过程,以 proc 关键字开始。比如:

  1. # 简单过程
  2. proc add(i, j: int): int =
  3. return i + j
  4. # 太过简单的过程
  5. proc multiply(i, j: int): int = i * j
  6. # 递归过程
  7. proc fibonacci(i: int) : int =
  8. if i <= 1: return i
  9. return fibonacci(i - 1) + fibonacci(i - 2)
  10. # 无返回值的过程
  11. proc greeting(name: string): void =
  12. echo "Hello, ", name
  13. # 无参数的过程,同时省略了 :void
  14. proc printSeparator() =
  15. echo "--------"

过程的使用

在使用过程时,参数值可以写到括号里,也可以不写到括号里。有时,为了防止歧义,参数值一定要写到括号里。比如:

  1. greeting("Tom") # 正常写法
  2. greeting "Tom" # greeting 后面的值被作为参数传入
  3. echo add(1, 2) # 1 和 2 作为 add 的两个参数传入 add 过程
  4. # add 的返回值被作为 echo 的参数传入 echo 过程
  5. echo add, 1, 2 # 歧义,出现编译时错误

参数

默认值

过程参数中的参数可以有个默认值:

  1. proc doSth(check: bool = true) =
  2. if check: echo "checked"
  3. else: echo "not checked"
  4. doSth()
  5. doSth false

参数的可变性

参数变量通常是不可变的:

  1. proc add2(i, j: int): int =
  2. i = i + j # 编译时错误,i 不可以被再赋值
  3. return i

但这并不是绝对的,可以通过把变量声明为可变的(加个 var)来改变这一点:

  1. proc divmod(a, b: int; res, remainder: var int) =
  2. res = a div b
  3. remainder = a mod b
  4. var x, y: int
  5. divmod(8, 5, x, y) # modifies x and y
  6. echo x # 1
  7. echo y # 3

命名参数

如果一个过程有很多个参数,光搞清楚这些参数的顺序就够头疼了。这时,可以用命名参数来拯救我们。假如有这样一个过程:

  1. proc createWindow(x, y, width, height: int; title: string;
  2. show: bool): Window =
  3. # implementation ommitted

下面两个过程调用等价,但是第一个看起来就让人很懵逼:

  1. var w = createWindow(0, 0, 600, 400, "My Application", true)
  2. var w = createWindow(show = true, title = "My Application",
  3. x = 0, y = 0, height = 600, width = 800)

我们还可以把参数顺序和命名参数混合起来使用。下面的调用和上面两个等价:

  1. var w = createWindow(0, 0, title = "My Application",
  2. height = 600, width = 800, true)

varargs

可以使用 varargs 来使过程接受很多个同类型的变量:

  1. proc sum(nums: varargs[int]): int =
  2. var result = 0
  3. for n in nums:
  4. result += n
  5. return result
  6. echo sum() # 输出 0
  7. echo sum(1) # 输出 1
  8. echo sum(1, 2, 3) # 输出 6

返回值

result 变量

在 Nim 的过程中,有一个隐式的 result 变量来表示过程的返回值。这个 result 变量使用默认值进行初始化。如果在过程求值过程中,使用了这个隐式的 result 变量,那么在返回计算结果时,可以用 return 来替代 return result

  1. proc sumTillNegative(x: varargs[int]): int =
  2. for i in x:
  3. if i < 0:
  4. return
  5. result = result + i

由于 result 是隐式的,所以如果你在过程中显式声明了 result 变量的话,这个隐式的 result 就不起作用了。

可丢弃返回值

函数的返回值是不可以直接丢掉的。如果要丢掉,可以使用 discard 语句:

  1. proc combine(i, j: int): int =
  2. echo "combined two integers"
  3. return i + j
  4. combine(1, 2) # 不可以
  5. discard combine(1, 2) # 可以

如果想要优雅地丢弃返回值,可以把返回值声明为可丢弃的:

  1. proc combine(i, j: int): int {.discardable.} =
  2. echo "combined two integers"
  3. return i + j
  4. combine(1, 2) # 可以

过程重载

Nim 支持 C++ 那样的过程重载:

  1. proc toString(x: int): string =
  2. result =
  3. if x < 0: "negative"
  4. elif x > 0: "positive"
  5. else: "zero"
  6. proc toString(x: bool): string =
  7. result =
  8. if x: "yep"
  9. else: "nope"
  10. assert toString(13) == "positive" # calls the toString(x: int) proc
  11. assert toString(true) == "yep" # calls the toString(x: bool) proc

运算符/操作符

运算符本质上就是一个拥有特殊名称(名称是操作符)的过程。Nim 中操作符分为两类:

  • 中缀操作符,比如 +a+b)、*a*b)、< (a<b) 等
  • 前缀操作符,比如 ++a)等

除了內建的关键字 andornot 之外,操作符通常包含这些字符:\/<>=@$~&%!?^.|。没有什么可以阻止你用上面的字符来定义自己的运算符,但是如果不恰当地使用,通常会影响代码可读性。

定义操作符的方式和定义普通过程差别不大,不过需要用操作符作为过程名,同时要把操作符用 ``` 括起来:

  1. proc `$` (x: myDataType): string = ...
  2. # 现在 $ 操作符应用到 myDataType 类型的变量上了

我们甚至可以使用 ``` 来把操作符像普通过程那样使用:

  1. if `==`( `+`(3, 4), 7): echo "True"

前置声明

和 C/C++ 一样,在 Nim 中要使用一个名词,需要先声明出来。比如:

  1. proc sum(i, j, k: int): int = add(i, add(j, k)) # 不行,add 没有在 sum 之前声明
  2. proc add(i, j: int): int = i + j

要使这段代码正常编译,有一下两种方式改造:

  1. # 方式一:把 add 定义到 sum 前面
  2. proc add(i, j: int): int = i + j
  3. proc sum(i, j, k: int): int = add(i, add(j, k))
  4. # 方式二:先把 add 声明出来
  5. proc add(i, j: int): int # 先声明有这样一个 add 过程
  6. proc sum(i, j, k: int): int = add(i, add(j, k))
  7. proc add(i, j: int): int = i + j

对于这个简单的例子,使用方式一其实就够了。但是对于某些情况,比如递归调用,就只能用方式二:

  1. # 先把 even 过程声明出来
  2. proc even(n: int): bool
  3. proc odd(n: int): bool =
  4. assert(n >= 0) # makes sure we don't run into negative recursion
  5. if n == 0: false
  6. else:
  7. n == 1 or even(n-1)
  8. proc even(n: int): bool =
  9. assert(n >= 0) # makes sure we don't run into negative recursion
  10. if n == 1: false
  11. else:
  12. n == 0 or odd(n-1)

这种预先把过程声明出来,而在后面给出具体实现的方式就是声明前缀。