Language Overview
Nim 是一种静态类型语言,具有强大的语言能力、优美的语法和设计合理功能强大的toolchain。 这些优点主要是由于这个语言比较新,吸收了很多其他语言的经验。 开发完全在 github 上进行,社区非常活跃。
nim 提倡使用 immutable data structure, 关键字 let
声明的变量都是不可变的,
需要用 var
说明变量是可变的。
通过 nimpy 可以方便地在 nim 和 Python 间双向调用, Python 作为程序主体,性能瓶颈由 nim 代码编译成 library (.so)文件供 Python 调用。
或者用 nim 写主程序外壳,调用 Python 库, 但 nim 不会编译 Python 库,运行环境中需要配置好 Python 需要的路径。
目前唯一的弱点是 REPL 不好用, 所以 nim 语言不适合交互式开发,而适合针对一个特定功能的、以自动化单元测试为导向的开发模式。
Nim for Python Programmers 是一份非常好的上手资料。
Run Script
nim 的输出位置参数居然放在了 --fullhelp
的 Advanced options
里,
--help
没有,查找方法:nim --fullhelp | ag -- '-o'
,
设置输出目录,并运行编译结果:
nim c --outdir:build src/client.nim && build/client aabb
,
nim 编译时输出的 hints 好像没什么用,加 --hints:off
关闭:
nim c --outdir:build --hints:off src/client && build/client aabb
.
或者定义下面的别名快速运行 nim 脚本:
alias nr="nim c -r --verbosity:0"
,脚本后面直接加上命令行参数即可,
例如:nr dsnote.nim s nim thunder
。
Build Binary
Compile nim source codes with -d:release
and -d:danger
(with --opt:speed
) can promote performance of
the compiled binary significantly.
When search string nim
case-insensitively in about 1900 text files,
grep -i -l nim ~/.donno/repo/*.md | wc -l
cost about 0.03s.
While nim binary in debug mode costs about 0.14s,
binary compiled with -d:release
costs about 0.045s,
and binary with -d:danger
costs about 0.035s,
on Linux Mint 19.3 on my Dell laptop
(E7450, Intel i7-5600U 2.60GHz, 4 cores, 8GB memory, SSD).
Here danger means "turns off all runtime checks and turns on the optimizer". Get more details in section "Additional compilation switches" of Nim Compiler User Guide
Development
开发环境的整体布局仍然是左右两个窗口,左边仍然是 editor, 右边不再是 REPL,而是文件内容变化时自动编译:
ls dsnote.nim | entr nim c dsnote.nim
Note:
-c
option of nim compile
makes the source compiling without
asmbling or linking, which makes the response to the developer faster.
Editor support
vim plugin github:alaviss/nim.nvim together with prabirshrestha/asyncomplete.vim provides enough editor supports, including:
- Syntax highlight
- Auto indentation
- Autocompletion
- Show API of a function (when autocompletion)
See nim in vimrcs for details.
Under the hood, nim.nvim use nimsuggest
(which is part of Nim core)
to get available methods and properties of an object.
Debugging
Debugging with GDB
Hello World
- Compile nim code with
--debugger:native
option. For example:nim c --debugger:native adder.nim
; gdb adder
- Set breakpoint:
b adder.nim:3
; run
Debug TUI Application
sudo apt install gdbserver
nim c --debugger:native src/moe.nim
Debugging with echo
To get the value of variable status
,
add debugEcho("status:\n", repr(status), "\n===")
into the codes and run again:
nim c -r src/moe
Unittest
Script Style
$ cat << EOF > myadd.nim
func newadd(x: int, y: int): int = x * 10 + y
export newadd
EOF
$ cat << EOF > test_myadd.nim
import unittest
from myadd import newadd
suite "test my func":
test "myadd func":
check(newadd(4, 7) == 47)
EOF
$ nim c -r test_myadd
To export a function,
the export
command must appear after the function definition.
Add asterisk after a function name to make it public. So the following myadd.nim is equivalent with above one:
func newadd*(x: int, y: int): int = x * 10 + y
You can use relative path in import
statement.
For example: import ./myadd
works fine.
Project Style
nimble init myproj
cd myproj
nimble test
You can modify tests/test1.nim to make the test fail deliberately.
Python Interoperation
The following is a demo based on README.md of nimpy:
$ nimble install nimpy
$ cat << EOF > myadd.nim
import nimpy
func add(x: int, y: int): int {.exportpy.} =
return 100 * x + y
EOF
$ nim c --threads:on --app:lib --out:myadd.so myadd
$ cat << EOF > main.py
import myadd
print(myadd.add(3, 5))
EOF
$ python main.py
305
You can also import myadd
within IPython console.
Note: func
in nim is the side-effect-free version of proc
.
Structured Types
Slice
A slice has a form a .. b
, inclusive.
You can use ^n
to represent reverse slicing.
For example ^1
is the last item in a sequence, like -1
in Python.
^2
is the last 2nd item, and so on.
So @[1, 3, 5, 2, 9][2 .. ^2]
means the 3rd to the last 2nd items,
which is @[5, 2]
.
Array, Sequence, Set, Tuple & Table
- Array: 固定长度,元素类型相同,例如
[e1, e2]
- Sequence: 不固定长度,元素类型相同,例如
@[e1, e2]
- Set: 固定长度,元素类型相同且不能重复,例如
{e1, e2}
- Tuple: 固定长度,元素类型不同,例如
(name: "Peter", age: 30)
- Table: 实际上是元素类型为二元 tuple 的 array,
例如
{"key1": "value1", "key2", "key3": "value2"}
, 实际是[("key1", "value1"), ("key2", "value2"), ("key3", "value2")]
的语法糖。
Tuple 与 object 的区别
Tuple 的字段都是公开的,不能继承(没有父子关系),
字段名称、顺序、类型都相同的 tuple 彼此相等,
声明变量方法:person = (name: "Peter", age: 30)
,
或者简写为 person = ("Peter", 30)
Object 的字段是隐藏的,除非名字以 *
结尾。
能够声明父类,即使字段名称、顺序、类型都相同,不同 object 不相等。
声明变量方法:let person = Person(name: "Peter", age: 30)
,
不能像 tuple 那样可以省略字段名。
Type System
Object variants
Object variants 让对象有一个特殊的 标记 字段, 决定其他字段是否存在,比如手册里展示的 Node 对象, 其 kind 是标记字段,当 kind = nkIf 时,表示这个节点是个 if 语句, 有 condition, thenPart, elsePart 3 个字段, 而当 kind = nkInt 时,则只有 intVal 一个字段。
Object variants 相当于普通 OOP 语言的一个抽象父类 + 几个具体实现子类, 父类包含各个子类的共同字段。
Pointer type
ref
是 GC-traced 指针,ptr
是 untraced 指针,需要手工分配和回收内存。
[]
或者 .
取指针变量的值。
指针变量的默认值是 nil,
可以在类型声明中用 not nil
保证指针不为 nil。
Procedural type
Procedural type 是指向 proc 的指针。
Distinct type
用货币的例子很好解释了 distinct type 的用途:适合物理量单位的定义, 比如类型 Dollar 虽然行为很像 int,但不是 int, 不适用父类和子类之间的关系。
auto type
用于让 compiler 自动推断 proc 参数或者返回值的类型。
Statement and expression
Statement 分为 simple statement 和 complex statement, 前者内部不能再包含 statement,例如赋值、return 等,后者则可以包含 statement。
Statement list expression & block expression
在需要 expression 的场合,可以用 (statement1; statement2; expr)
代替此
expression,这种技术在调试程序时很有用。
与之类似的是 block expression,二者都是用 block 内最后一个表达式的值作为整个 block 的值,区别在于 statement list expression 不会创建新的 scope, 而 block expression 创新新的 scope(意味着 block 内声明的变量只在 block 内有效)。
var and let
var
声明一个变量,let
声明一个值到变量的绑定,且不允许变量重新赋值。
const
声明的值在编译期计算,static
段中的语句在编译期执行。
return and yield
return
用于函数返回值,yield
用于 iterator 向调用者返回一个值。
其他语句
discard
相当于 Python 的 pass
,可以作为空语句的占位符。
when
类似于 if
,但前者用于条件编译,后者用于普通流程控制。
block
用于命名代码块,以便于 break <block-name>
从指定代码块里退出。
asm
用于在 nim 代码中插入汇编语言。
using
用于提供变量的默认类型,避免反复声明变量类型
cast
:强制类型转换,例如 cast[int](x)
Procedures
通过定义 f=
proc 实现对象的 setter.
访问了 enclosing scope 中的 local variable 的 nested procs 就是 closure。
没有名字的 proc
声明相当于 lambda expression,例如:
var cities = @["Frankfurt", "Tokyo", "New York", "Kyiv"]
cities.sort(proc (x,y: string): int = cmp(x.len, y.len))
func
是没有 side effect 的 proc.
proc 参数默认采用值传递,过程内修改参数值不会影响 caller 里的值,
但可以用 a: var int
将参数 a
声明为引用传递,这时过程内修改会影响 caller 内的值。
Multi-methods 指相同名称的 method,参数类型不同。
REPL
nimble install inim
To run binary packages installed by nimble,
you need to add ~/.nimble/bin into $PATH
manually.
See section "Nimble's folder structure and packages"
in readme of nimble.
nim on Windows
First download MinGW and extract to a folder, add the folder to %PATH%.
Then download choosenim (0.5.1 for now) for Windows and extract it. Open a console in this folder and run:
set HTTP_PROXY=http://127.0.0.1:1080
runme.bat
When installation completed, add C:\Users\
Now you can use nim
and nimble
in console.
Data Science
NimData provides data frame for data analysis, with remarkable performance compared with pandas.
For there's no REPL, nim is by far not suitable for data explorations, but implementations of specific algorithm.