Nim教程翻译(三)
来源:互联网 发布:小偶的软件 编辑:程序博客网 时间:2024/06/11 03:18
原文链接:http://nim-lang.org/docs/tut2.html
Nim Tutorial (Part II)
作者:Andreas Rumpf
版本:0.11.2
介绍
"Repetition renders the ridiculous reasonable." -- Norman Wildberger
“重复使荒谬的合理。”--Norman Wildberger
这个文件是Nim编程语言构建的一个高级教程。请注意,这个文档作为手册是有些过时的,但是它包含很多具有高级编程语言特征的例子。
编译指示
编译指示是nim的方法给编译器的附加信息或者命令,没有引入大量的新关键字。编译指示是用特殊的点和大括号{. 和 .}括起来的。这个教程不包括编译指示。看手册(Manual)或用户指南( user guide)描述了可用的编译指示。
面向对象编程(OOP)
nim支持面向对象编程(OOP)是极保守行动,可以使用功能强大的面向对象技术。面向对象的程序设计是设计一个程序的一种方式,并不是唯一的方法。通常一个程序的方法将产生更简单和高效的代码。特别地,相比继承,组合往往是更好的设计。
对象
就像元组,对象是一种手段以一种结构化的方式将不同的值包装在一起。对象提供了很多元组没有的功能。对象提供继承和信息隐藏。由于对象封装数据,T()对象构造器应该只用于内部,程序应该提供一个过程用于初始化对象(这被叫做构造器)。
对象在运行时访问他们的类型。of操作符,可以用来检查对象的类型:
从外部特定的模块可以访问到的对象域必须用*标记。相比之下,元组的不同的对象类型从来是不等价的。新的对象类型只能在type部分定义。
继承是处理对象的语法。现在还不支持多继承。如果一个对象类型没有合适的祖先,RootObj可以作为它的祖先,但这只是一个约定。没有祖先的对象是隐藏的final。你可以用inheritable编译指示来产生一个除了来自system.RotObj之外的的根对象。(例如:这被用在GTK包)。
每当使用继承时应使用ref对象。它不是绝对必要的,但是用non-ref对象赋值,如:let person: Person = Student(id: 123)将截断子类域。注意:组合(has-a 关系)往往优于继承(is-a 关系)为了简单的代码重用。由于在nim中对象是一种值类型,组合和继承一样有效。注:(引用类型(重量级对象)和值类型(轻量级对象))
相互递归类型
对象,元组和引用可以塑造相当复杂的数据结构相互依赖彼此;它们是相互递归。在nim中这些类型只能在一个单一的类型部分声明。(其他任何需要任意前端符号会减慢编辑。)
Example:
类型转换
nim区分显示的类型转换和隐式的类型。显示的类型转换用casts操作符并且强制编译器解释一种位模式成为另一种类型。
隐式的类型转换是一个更礼貌的方式将一个类型型转换为另一个:他们保存摘要值,不一定是位模式。如果一个类型转换是不可能的,编译器控诉或者抛出一个异常。
类型转换语法是:destination_type(expression_to_convert)目的类型(要转换的表达式)(像一个普通的调用)
如果x不是一个Student类型,会抛出InvalidObjectConversionError异常。
对象变形
通常一个对象层次结构在特定的情况下是不必要的,需要简单的变体类型。
一个例子:
可以从这个例子中看到,一个对象层次结构的一个优点是,不需要不同的对象类型之间的转换。然而,访问无效的对象域会引发一个异常。
方法
在普遍的面向对象程序设计语言中,过程(也叫做方法)被绑定到一个类。这种做法有缺点:
- 程序员无法控制添加一个方法到一个类中是不可能的或者需要丑陋的解决方法。
- 很多情况下方法应该属于哪里是不清楚的:是加入一个字符串方法还是一个数组方法?
nim通过不分配方法到一个类中避免了这样的问题。所有的方法在nim中都是多方法。后面我们将看到,多方法区别与过程只为了动态绑定目的。
方法调用语法
对于调用例程有一个语法糖:可以用语法obj.method(args)而不是method(obj,args).如果没有剩余的参数,圆括号可以省略:obj.len(而不是len(obj))。
这个方法调用语法是不受对象限制的,它可以被用于任何类型。
(另一种方式来看待方法调用语法是它提供了缺失的后缀表示法.)
所以纯面向对象代码是容易写的:
特性
如上面的例子所示,nim没必要get-properities:通常get-procedures被称为方法调用语法实现相同的功能。但是设定的值是不一样的;对于这需要一个特殊的setter语法:
(这个程序也展示了inline程序)
[]数组访问运算符可以重载以提供数组属性:
这个例子是愚蠢的,因为一个vector通过一个元组可以更好的模拟,元组已经提供v[]访问。
动态调度
程序总是使用静态调度。对于动态调度使用method代替proc关键词:
注意:在例子中,构造器newLit和newPlus是过程,因为对于它们使用静态绑定更有意义,但是eval是一个方法因为它需要动态绑定。
在一个多方法的所有参数中有一个对象类型用于调度:
如上面那个例子所示,调用一个多方法不能是模棱两可的。相比collide 1,collide 2是首选,因为决议是从左到右工作的。因此,Unit, Thing优于Thing, Unit。
注意:nim不产生虚拟方法表,但是生成调用树。这样为方法调用和使用内联避免了多余的间接分支。然而,其他的优化像:编译时间评估或者死代码消除对于方法是不起作用的。
异常
在Nim中异常是对象。按照惯例,异常类型带有'Error'后缀。系统模型定义了一种异常层次结构,你要遵守。异常源自system.Exception,它提供公共的接口。
异常必须在堆上分配,因为它们的生存时间不确定。编译器将阻止你在栈空间创建异常。所有引发的异常应该在msg域说明原因。
一个约定是异常要在特殊的情况下才能被引发:例如,如果一个文件不能打开,这不应该引发一个异常因为这是很常见的(文件可能不存在)
raise语句
使用raise语句引发一个异常:
如果raise关键字没有在一个异常后面,最后的异常将会被引发。为了达到避免重复公共代码模式的目的,可以使用系统模块中的模板newException:
try语句
try语句处理异常:
除非引发一个异常,try之后的语句才会执行。然后会执行except相应的部分。
如果这有一个异常没有明确的列出,将会执行空的except部分。它类似于if语句中的else部分。
如果存在finally部分,在异常处理之后它必须执行。
异常是在except部分处理,如果一个异常没有处理,它是通过调用堆栈传播。那意味着通常剩下的程序-不在一个finally的部分--是不执行的(如果异常发生)。
如果你需要访问实际的异常对象或者except分支中的消息,可以使用系统模型中的getCurrentException()和getCurrentExceptionMsg()的方法。例如:
注释过程提出异常
尽管使用可选项{.raises.}编译注释你可以确定一个过程是为了引发一组特定的异常,或者根本什么都没有。如果使用{.raises.}编译注释,编译器会验证这是正确的。例如:如果你指定一个过程引发IOError,在某些地方它(或者是它调用的方法之一)开始引发一个新的异常,编译器将会阻止过程编译。使用示例:
一旦你在程序中有这样的代码,如果引发异常的列表发生改变,编译器将会停止,在过程特定的地方出现一个错误,过程将停止验证语用以及不能捕获引发的异常,随着文件以及行未捕获的异常被引发,它可能帮助你找到异常改变的问题代码。
如果那你想为现有的代码添加{.raises.}编译注释,编译器也会帮组你。你可以添加{.effects.}编译注释语句都你的过程中,编译器将会输出所有已经推断的影响达到那一点(异常轨迹是nim的影响系统的一部分)。另一种间接地方法通过一个过程找到引发的异常列表是使用nim doc2命令,它为了整个模块生成文档以及用引发异常列表装饰所有的过程。你可以在手册中阅读更多关于nim的影响系统以及相关的语法。
泛型
泛型是nim参数化过程的手段,迭代器或者类型有类型参数。他们对于有效类型安全容器是非常有用的:
上面的例子展示了一个通用的二叉树。根据上下文,括号是用来介绍类型参数或者实例化一个通用过程,迭代器,或类型。如例子所示,泛型伴随重载:最合适的add方法被使用。内置的序列的add方法不是隐藏的,它被应用在perorder迭代器中。
模版
模版是一个简单的替换机制在nim的抽象语法树上操作。模版在编译器的语义分析阶段被处理。它和其他的语言整合的很好,分享没有c语言的预处理宏定义的缺陷。
调用一个模版,就像调用一个程序一样。Example:
!=, >, >=, in, notin, isnot 操作都是模版。如果你重载==操作符这是很有益处的,!=操作符可以自动访问和做正确的事情。(除了IEEE浮点数-NaN打破基本的布尔逻辑)
a>b被转换成b<a。a in b被转换成contains(b,a)。notin和isnot有显而易见的含义。
模版对于懒惰评价的目的是非常有用的。 考虑一个简单的logging过程:
这个代码有一个缺点,如果debug被设置为false,$和&操作符依然执行。(对于过程的参数评估is eager)
将log函数转换成模版解决这个问题:
模版的参数类型可以是普通类型或者元类型expr(代表表达式),stmt(代表声明),typedesc(代表类型描述)。如果模版没有显示的返回类型,声明用于过程和方法的一致性。
如果这是一个stmt参数,它应该被放到模版声明的最后面。原因是statements可以通过一个特殊的:语法传递给模版。
例子中的两个writeln语句被绑定到body参数。withFile样板包含的样板代码有助于避免常见的错误:忘记关闭文件。注意:let fn=filename语句如何确保文件名只被计算一次。
宏
宏使先进的编译时代码转换,但是它并不改变nim的语法。然而,这没有真正的限制,因为毕竟nim的语法是足够灵活的。宏必须在nim纯代码中实现如果外部函数接口(FFI)不在编译器中启用,但是除了那个限制(这一点在未来会消失),你可以写任何种类的nim代码,编译器将在编译的时候运行它。
有两种方法来创建一个宏,一种:生成nim的源代码,让编译器解析它;另一种是:你为编译器手动创建一个抽象语法树(AST)。为了创建AST,需要知道Nim怎样将具体语法转换为抽象语法树(AST)。AST在宏模块记录。
一旦宏创建完成,有两种方法调用它:
- 像调用过程一样调用宏(如:表达宏)
- 用特殊的macrostmt语法调用宏(声明宏)
表达宏
下面的例子实现了一个功能强大的debug命令,接受数目可变的参数:
宏调用拓展:
语句宏
声明宏的定义和表达宏一样。然而,它们通过一个表达式后跟一个冒号被调用
下面的示例概述,从一个正则表达式生成一个词法分析器的宏:
建立你的第一个宏
给一个footstart写宏,我们将展示如何将你的典型的动态代码转换为静态编译的代码,为了练习我们使用下面的代码片段作为出发点:
想必这段代码可以用在商业软件,读取配置文件展示买软件的人的信息。这个外部文件将通过一个网上购物车网站包含许可信息的程序生成:
readCfgAtRuntime过程将打开所给的文件名以及从tables模块返回一个表格。使用strutils模块的splitLines过程解析这个文件(不要过于关心处理无效的数据或者案例)。有很多事情可能失败;在意的目的是解释如何使这个在编译的时候运行,而不是如果正确实施一个DRM方案。
注:DRM,英文全称Digital Rights Management, 可以翻译为:内容数字版权加密保护技术。 由于数字化信息的特点决定了必须有另一种独特的技术,来加强保护这些数字化的音视频节目内容的版权,该技术就是数字权限管理技术---DRM(digital right management)。
作为一个编译时过程实现这个代码将允许我们移除data.cfg文件,我们需要沿着二进制,加上如果信息是不变的,它没有从一个逻辑角度使它在一个全局变量范围内变动,如果它是一个常数会更好。最后,可能最有价值的功能,我们可以在编译时实现一些验证。你可以将这看做是一个好的测试单元,因为除非所有的事情都是正确的,否则它不可能包含一个二进制,防止你传递给用户一个不完整的程序
它不会启动因为一个小关键文件的丢失或者它的内容被错误的更改成一些无效的东西。
生成源代码
我们的第一次尝试将通过修改程序生成一个编译时字符串用源代码生成,然后我们将它从宏模块中传递到parseStmt过程。下面是修改的源代码实现宏:
12345678910111213141516171819202122232425
好消息是没有改变很多!首先,我们需要改变输入参数的处理(line 3)。在动态版本中readCfgAtRuntime过程接收一个string参数。然而,在宏版本中它同样是声明为string类型,但是这是宏的外部接口。当宏运行,它实际上得到一个PNimNode对象而不是一个字符串,所以我们必须从宏模块调用strVal过程(line 5)得到字符串被传递到一个宏中。
第二,我们不能使用来自系统模型的readFile过程由于在编译时外部函数接口(FFI)的限制。如果我们试图使用readFile过程,或者其他依赖于FFI的过程,编译器将会带有不同评估的错误信息以及转储宏的源代码,以及一个在救助之前编译器达到的堆栈跟踪。我们可以摆脱这个限制通过使用来自系统模型的slurp过程,它正是为编译时所准备的。(就像groge执行外部程序以及捕捉它的输出)
有趣的事情是我们的宏并不返回一个运行时表对象。相反,它建立了nim源代码进入source变量。对于配置文件的每一行,将会产生一个const变量(line 15).为了避免歧义我们在这些变量的前面加上cfg前缀。编译器做的是用以下的代码片段替换调用宏的那一行。
你可以自己验证这个,添加echo(source)行在宏的结束然后编译程序。另一个不同是不是调用通常的quit过程来终止(我们依然可以调用),这个版本调用erroc过程(line 14).error过程与quit过程有相同的行为,但是error过程将会转储source和文件行信息在错误发生的地方,这使得对于程序员更容易找到编译失败的地方。在这种情况下,它会指向调用宏行,而不是我们正在处理的data.cfg文件行,这是宏本身需要控制的事情。
手动生成AST
为了生成一个AST我们需要清楚的了解使用的结构通过nim编译器暴露在宏模块,起初看起来这是一个艰巨的任务。但是我们可以使用作为辅助捷径的dumpTree宏,它被作为一个声明宏而不是一个表达式宏。既然我们知道我们想生成一个const符号串,我们可以创建下面的源文件然后编译它,看看编译器从我们期望什么:
在编译源代码的期间,我们可以在输出中看到以下行(再次说明,由于这是一个宏,编译是足够的,你不需要运行任何二进制文件):
有这样的输出对于编译器期望怎样的输入我们有一个更好的主意。我们需要生成一个语句列表。对于每一个恒定的源代码生成一个ConstSection和一个ConstDef。如果我们把所有恒定的部分移动到一个单const块中,我们可以看到仅仅一个单一的ConstSection带有三个孩子。
也许你没有注意,但是在dumpTree例子中第一个恒定式显示的限定了恒定式的类型。这是为什么在输出树中后两个恒定式它们的第二个孩子为空,但是第一个恒定式有一个string标识符。因此基本上一个常量的定义是由一个标识符,一个可选的类型(可以为空)以及值组成。有了这方面的知识,让我们看看完整版本的AST建立宏:
123456789101112131415161718192021222324252627282930
既然我们是建立在前面例子生成的源代码中,我们将只提及与它不同的地方。相比创建一个临时的字符串变量以及写入它的源代码好像它是被手动写的,我们直接使用result变量以及创建一个语句列表节点(nnkStmtList)它将容纳我们的孩子(line 7)。
对于输入的每行我们必须创建一个常数定义(nnkConstDef)以及将它包在一个常数部分(nnkConstSection)。一旦这些变量被创建,我们分等级地填补它们就像前面的AST树展示的那样:constant定义是section定义的孩子,以及constant定义有一个标识符节点,一个空节点(我们让编译器识别类型),和带有一个字符串字面值的值。
当写一个宏的时候最后一个提示:如果你不确定你建立的AST看起来很ok,你可能会使用dumpTree宏。但是你不能在你写的或者调试的宏中使用它。相反通过treeRepr产生字符串。如果在这个例子的最后你添加echo treeRepr(result),你可以得到和使用dumpTree宏时相同的输出,但是当然你可以在宏的任何你可能有困难的地方调用它。
至此Nim Tutorial(partI)和Nim Tutorial(partII)都已经翻译完毕,翻译过程中或多或少会存在一些问题,欢迎指正!
- Nim教程翻译(三)
- Nim教程翻译(二)
- Nim教程翻译(一)
- Rabbitmq教程翻译(三)Publish/Subscribe
- Unity3D Shader官方教程翻译(三)
- POJ 2068 Nim 已翻译
- POJ 2975 Nim 已翻译
- Nim教程(I )
- Java 3D API官方教程[翻译三]
- [翻译]asp.net ajax xml-script教程(三)
- [翻译]asp.net ajax xml-script教程(三)
- Java 3D API官方教程[翻译三]
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- Unity3D Shader官方教程翻译(三)----Shader语法:属性
- CSS vertical-align的深入理解(二)之text-top篇
- c++ primer 学习笔记(2)迭代器
- Eclipse中自动提示的参数变成arg0,arg1的解决办法
- 关于STM32的IAP在线升级的整理
- Hadoop的简单控制台log分析
- Nim教程翻译(三)
- 多线程还是多进程的选择及区别
- jQuery.extend 函数详解
- Android 5.1 源码学习之SurfaceFlinger的启动(一)
- SIEMENS SIMATIC S7-200 PLC 开发环境配置。
- 成长,随遇而安
- Android动态添加控件约束位置
- gdb调试命令
- Xcode在真机调试的时候出现"The identity used to sign the executable is no longer valid"