过程
为了在例子中定义新的命令如echo和readLine,需要过程的概念。(有些语言中叫做方法或者函数)。在nim中新过程用proc关键字定义:
proc yes(question: string): bool =
echo(question, " (y/n)")
while true:
case readLine(stdin)
of "y", "Y", "yes", "Yes": return true
of "n", "N", "no", "No": return false
else: echo("Please be clear: yes or no")
if yes("Should I delete all your important files?"):
echo("I'm sorry Dave, I'm afraid I can't do that.")
else:
echo("I think you know what the problem is just as well as I do.")
这个程序展示了一个名字是yes的过程,它问了用户一个问题,如果他回答“yes”(或者其他相似的答案)返回true,如果他回答“no”(或者其他相似的答案)返回false。
一个return语句直接离开过程(和while循环)。这个(question: string): bool语法描述的是这个过程有一个名字为question的参数,参数类型为字符串类型,返回一个bool类型的值。bool类型是一个内置类型:bool类型唯一有效的值是true和false。if或者while语句中的条件语句就应该为bool类型。
一些专业术语:在例子中question叫做(正式)参数,"Should I..."叫做实参传递给这个形式参数
结果变量
一个过程返回一个值有一个隐式的结果变量声明,它代表返回值。一个没有表达式的返回语句是一个返回结果的简称。结果值总是自动返回在一个过程的末尾如果在出口没有返回语句。
proc sumTillNegative(x: varargs[int]): int =
for i in x:
if i < 0:
return
result = result + i
echo sumTillNegative() # echos 0
echo sumTillNegative(3, 4, 5) # echos 12
echo sumTillNegative(3, 4 , -1 , 6) # echos 7
result变量已经隐式的声明在函数开始处,所以再次声明,例如:'var result',将会存在一个与result相同名字的正常变量。result变量也已经初始化为类型默认值。注意:引用数据类型将会是零在过程开始处,因此可能需要手动初始化。
参数
在过程中参数是恒定的。默认情况下,它们的值不能改变因为这允许编译器以一种更有效的方式实现参数传递。如果在过程中需要一个易变的变量,它必须在过程中用var声明。与参数相同的名字是可以的,事实上这是一个惯用语法。
proc printSeq(s: seq, nprinted: int = -1) =
var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len)
for i in 0 .. <nprinted:
echo s[i]
如果对于调用者需要修改过程参数,需要用到一个var参数:(就像其他语言,值调用是不会改变实参的值,要想改变实参的值要通过引用调用,nim中是在过程中将参数用var声明就可以实现调用参数的改变)
proc divmod(a, b: int; res, remainder: var int) =
res = a div b # integer division
remainder = a mod b # integer modulo operation
var
x, y: int
divmod(8, 5, x, y) # modifies x and y
echo(x)
echo(y)
在例子中,res和remainder是var参数。var参数可以通过过程修改,这个改变对于调用者是可见的。注意:上面的例子使用元组作为返回值而不是使用var参数作为返回值将会更好。
discard语句
调用一个带有返回值的过程仅仅是为了它的其他的作用,忽视它的返回值,一个discard语句必须使用。nim不允许默默地丢掉一个返回值:
discard yes("May I ask a pointless question?")
返回值会隐式的忽略如果调用的过程或者迭代器已经声明discardable编译指示:
proc p(x, y: int): int {.discardable.} =
return x + y
p(3, 4) # now valid
discard语句也可以在注释部分创建作为块注释的描述。
参数命名
一个过程通常有多个参数,而且参数出现的顺序并不是明确的。为一个过程构造了一个复杂的数据类型是非常正确的。因此一个过程的参数可以是命名的,以至于哪个实参属于哪个形参是明确的。
proc createWindow(x, y, width, height: int; title: string;
show: bool): Window =
...
var w = createWindow(show = true, title = "My Application",
x = 0, y = 0, height = 600, width = 800)
现在我们使用命名参数去调用createWindow过程的顺序参数已经没有问题了。命名参数和顺序参数的混合也是可以的,但是没有可读性:
var w = createWindow(0, 0, title = "My Application",
height = 600, width = 800, true)
编译器检查每个形参确切地接收一个实参。
默认值
为了使createWindow过程易于使用它应该提供默认值,这些值将作为实参值如果调用者没有明确的指定它们:
proc createWindow(x = 0, y = 0, width = 500, height = 700,
title = "unknown",
show = true): Window =
...
var w = createWindow(title = "My Application", height = 600, width = 800)
现在调用createWindow仅仅需要不同于默认值的值。
注意:类型推断作用于参数有默认值;例如:这里没有必要写title: string = "unknown"。
过程重载
nim提供了与c++相似的重载过程的功能:
proc toString(x: int): string = ...
proc toString(x: bool): string =
if x: result = "true"
else: result = "false"
echo(toString(13)) # 调用toString(x: int)过程
echo(toString(true)) # 调用toString(x: bool)过程
(注意在nim中toString通常是$操作符)对于toString的调用编译器会选择最合适的过程。这个重载解析算法具体是怎样工作的不在这里讨论(它将在手册中详述)。然而,它不会引起糟糕的意外,它是基于一个非常简单的统一算法。模糊不清的调用将会报错。
操作符
nim库大量使用重载--对于这中情况一个原因是每一个操作符比如+只是一个重载的过程。解析器允许你以中缀表示法或者前缀表示法使用操作符。一个中缀操作符总是接收两个参数,一个前缀操作符只有一个参数。后缀操作符是不可能的,因为那是模棱两可的。例如:a @ @ b 意味着 (a) @ (@b) 还是 (a@) @ (b)?它总是意味着(a) @ (@b),因为在nim中没有后缀操作符。
除了一些内置的关键字操作符例如:and,or,not
操作符通常包含这些字符: + - * \ / < > = @ $ ~ & % ! ? ^ . |
用户定义的操作符是允许的。没什么阻止你定义你自己@!?+~操作符,但是可读性会遭到破坏。
操作符的优先级是由它的第一个字符决定的。相关细节可以在手册中找到。
为了定义一个新的操作符,用反单引号括起操作符:
proc `$` (x: myDataType): string = ...
#现在,$运算符还可以处理myDataType,重载解析
#确保$与以前一样适用于内置类型。
"``"标记可以用来调用一个操作符就像调用其他过程。
if `==`( `+`(3, 4), 7): echo("True")
提前声明
每个变量,过程等,在它可以使用之前需要声明。(这是编译效率的原因)。然而,对于相互递归过程不需要提前声明。
# forward declaration:
proc even(n: int): bool
proc odd(n: int): bool =
n == 1 or even(n-1)
proc even(n: int): bool =
n == 0 or odd(n-1)