DarkMatter in Cyberspace
  • Home
  • Categories
  • Tags
  • Archives

Nim Notes


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

  1. Compile nim code with --debugger:native option. For example: nim c --debugger:native adder.nim;
  2. gdb adder
  3. Set breakpoint: b adder.nim:3;
  4. 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\.nimble\bin to %PATH%.

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.



Published

Feb 17, 2020

Last Updated

Feb 4, 2021

Category

Tech

Tags

  • nim 2
  • nimlang 1

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor