原文: http://hookrace.net/blog/what-is-special-about-nim/javascript
Nim 編程語言 很讓人振奮. 官方教程雖然很棒, 但只是慢吞吞介紹語言.
而我打算快速向你棧是用 Nim 能作的, 在其餘語言很難或者作不到的事情.html
我發現 Nim 是在我爲開發遊戲(HookRace)尋找一個正確的工具的時候,
這個遊戲是個人 DDNet 遊戲(mod of Teeworlds)後續的版本.
由於我最近忙着別的項目, 因此這個博客主要就關於 Nim 了, 直到我有時間繼續開發遊戲.java
好吧, 這個部分不見得有意思, 但我邀請你跟着文章一塊兒來執行代碼:node
nimfor i in 0..10: echo "Hello World"[0..i]
這以前, 安裝 Nim 編譯器.
將代碼保存爲 hello.nim
, 用 nim c hello
編譯, 再用 ./hello
運行二進制文件.
要同時編譯和運行, 使用 nim -r c hello
.
要使用優化過 release build, 而不是 debug build 的話, 使用 nim -d:release c hello
.
上面全部的配置你均可以看到下面的輸出:python
H He Hel Hell Hello Hello Hello W Hello Wo Hello Wor Hello Worl Hello World
要實現一個高效的 CRC32 程序你須要查一個表.
你能夠在運行時計算, 或者在源碼當中使用 magic array 寫好.
咱們這裏明確一下不想要任何的 magic number 出如今代碼中, 因此(這時候)咱們在運行時作:git
nimimport unsigned, strutils type CRC32* = uint32 const initCRC32* = CRC32(-1) proc createCRCTable(): array[256, CRC32] = for i in 0..255: var rem = CRC32(i) for j in 0..7: if (rem and 1) > 0: rem = (rem shr 1) xor CRC32(0xedb88320) else: rem = rem shr 1 result[i] = rem # Table created at runtime var crc32table = createCRCTable() proc crc32(s): CRC32 = result = initCRC32 for c in s: result = (result shr 8) xor crc32table[(result and 0xff) xor ord(c)] result = not result # String conversion proc $, automatically called by echo proc `$`(c: CRC32): string = int64(c).toHex(8) echo crc32("The quick brown fox jumps over the lazy dog")
好, 運行成功了, 咱們獲得輸出 414FA339
.
可是若是咱們能在編譯過程當中計算 CRC 表就更好了.
在 Nim 當中這很是容易, 替換掉 crc32table
的代碼, 咱們使用:github
nim# Table created at compile time const crc32table = createCRCTable()
是的, 就是這樣: 咱們索要作的僅僅是把 var
換成是 const
. 很妙吧?
咱們能夠寫一樣的代碼, 讓它在運行時, 或者編譯時執行. 不須要 template, 不須要元編程.編程
templates 和 macros 可用於替換模版, 在編譯時變換代碼.json
templates 僅僅在是在編譯時把調用它們的代碼換成它們的實際代碼.
咱們能夠本身定義一個循環:瀏覽器
nimtemplate times(x: expr, y: stmt): stmt = for i in 1..x: y 10.times: echo "Hello World"
那麼編譯器就會吧 times 循環的代碼變換爲普通的 for 循環:
nimfor i in 1..10: echo "Hello World"
若是你想問 10.times
的語法.. 它就是一個一般的 times
的調用,10
是這個調用的第一個參數, 後面跟着一個 block 做爲第二個參數
換句話說你也能夠寫 times(10);
, 參考統一的調用語法.
或者更輕鬆地初始化序列(變長的 array):
nimtemplate newSeqWith(len: int, init: expr): expr = var result = newSeq[type(init)](len) for i in 0 .. <len: result[i] = init result # 建立一個 2 維的序列, 大小爲 20,10 var seq2D = newSeqWith(20, newSeq[bool](10)) import math randomize() # 建立一個序列, 其中有 20 個隨機整數, 每一個小於 10 var seqRand = newSeqWith(20, random(10)) echo seqRand
macros 走得更遠異步, 讓你能分析還有操做 AST.
在 Nim 當中沒有列表剖析, 可是, 你能夠作到好比說[用 macro 把這個功能加進來].
那麼對於下面的代碼:
nimvar res: seq[int] = @[] for x in 1..10: if x mod 2 == 0: res.add(x) echo res const n = 20 var result: seq[tuple[a,b,c: int]] = @[] for x in 1..n: for y in x..n: for z in y..n: if x*x + y*y == z*z: result.add((x,y,z)) echo result
你能夠藉助 future 模塊寫成:
nimimport future echo lc[x | (x <- 1..10, x mod 2 == 0), int] const n = 20 echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), tuple[a,b,c: int]]
相對於優化本身的代碼, 你不會考慮把編譯器變得更聰明嗎? 在 Nim 你就能夠!
nimvar x: int for i in 1..1_000_000_000: x += 2 * i echo x
這些代碼(實際上沒啥用)課以經過教會編譯器兩種優化來加速:
nimtemplate optMul{`*`(a,2)}(a: int): int = let x = a x + x template canonMul{`*`(a,b)}(a: int{lit}, b: int): int = b * a
第一個是 term rewriting template 咱們指定 a * 2
能夠替換爲 a + a
.
第二個咱們指定乘法當中若是第一個是整型的字面量那麼 int
能夠被交換, 因而就有可能應用第一個 tempalte.
更復雜的模式也能夠實現, 好比優化 boolean 的邏輯:
nimtemplate optLog1{a and a}(a): auto = a template optLog2{a and (b or (not b))}(a,b): auto = a template optLog3{a and not a}(a: int): auto = 0 var x = 12 s = x and x # Hint: optLog1(x) --> ’x’ [Pattern] r = (x and x) and ((s or s) or (not (s or s))) # Hint: optLog2(x and x, s or s) --> ’x and x’ [Pattern] # Hint: optLog1(x) --> ’x’ [Pattern] q = (s and not x) and not (s and not x) # Hint: optLog3(s and not x) --> ’0’ [Pattern]
s
直接被優化爲 x
, 經過兩次連續的模式應用優化爲 2
, q
立刻獲得 0
.
若是你想看用 term rewriting tempalte 怎麼避免寫大整數的內存分配,
查一下 bigints 模塊 當中 opt
開頭的 templates:
nimimport bigints var i = 0.initBigInt while true: i += 1 echo i
由於 Nim 是編譯的 C 的, 外部函數接口頗有意思.
你能夠很容易用上 C 模塊庫當中你喜歡的函數:
nimproc printf(formatstr: cstring) {.header: "<stdio.h>", varargs.} printf("%s %d\n", "foo", 5)
或者使用你本身用 C 寫的代碼:
cvoid hi(char* name) { printf("awesome %s\n", name); }
nim{.compile: "hi.c".} proc hi*(name: cstring) {.importc.} hi "from Nim"
或者藉助 c2nim 使用任何你想要的:
爲了達到 soft realtime, 你能夠指定垃圾收集器何時還有多久被容許運行.
遊戲的主要邏輯在 Nim 當中能夠這樣實現, 用來避免垃圾收集器致使卡頓:
nimgcDisable() while true: gameLogic() renderFrame() gcStep(us = leftTime) sleep(restTime)
你常常用到數學的集合來容納你本身定義的值, 這是類型安全的寫法:
nimtype FakeTune = enum freeze, solo, noJump, noColl, noHook, jetpack var x: set[FakeTune] x.incl freeze x.incl solo x.excl solo echo x + {noColl, noHook} if freeze in x: echo "Here be freeze" var y = {solo, noHook} y.incl 0 # Error: type mismatch
你不會意外地加入另外一個類型的值. 集合的內部實現是一個高效的 bitvector.
對 array 來講能夠能夠的, 用 enum 來索引它們:
nimvar a: array[FakeTune, int] a[freeze] = 100 echo a[freeze]
這只是語法糖, 可是有的話是很好的. 在 Python 裏我常忘記 len
和 append
是函數是方法.
在 Nim 裏我不須要記憶, 由於兩種寫法是同樣. Nim 使用統一的調用語法,
這也被人提議給了 C++, 兩我的是 [Herb Sutter][HB] 和 Bjarne Stroustrup.
nimvar xs = @[1,2,3] # Procedure call syntax add(xs, 4_000_000) echo len(xs) # Method call syntax xs.add(0b0101_0000_0000) echo xs.len() # Command invocation syntax xs.add 0x06_FF_FF_FF echo xs.len
用 Nim 寫高效的代碼很容易, 這能夠在 [Longest Path Finding Benchmark][BPATH] 看出來,
其中 Nim 用至關漂亮的代碼完成了.
測試最開始發佈的時候我在本身的機器上作了一些測算:
(Linux x86-64, Intel Core2Quad Q9300 @2.5GHz, state of 2014-12-20)
代碼體積的壓縮使用了 gzip -9 < nim.nim | wc -c
. 也移除了 Haskell 中無用的代碼.
編譯時間就是完整的編譯, 若是你用 nimcache 緩存了標準庫的預編譯, Nim 就只要 323ms
我作過另外一個小的測試, 計算開頭 100M 天然數當中那些是質數, 對比 Python, Nim 和 C:
pythondef eratosthenes(n): sieve = [1] * 2 + [0] * (n - 1) for i in range(int(n**0.5)): if not sieve[i]: for j in range(i*i, n+1, i): sieve[j] = 1 return sieve eratosthenes(100000000)
nimimport math proc eratosthenes(n): auto = result = newSeq[int8](n+1) result[0] = 1; result[1] = 1 for i in 0 .. int sqrt(float n): if result[i] == 0: for j in countup(i*i, n, i): result[j] = 1 discard eratosthenes(100_000_000)
c#include <stdlib.h> #include <math.h> char* eratosthenes(int n) { char* sieve = calloc(n+1,sizeof(char)); sieve[0] = 1; sieve[1] = 1; int m = (int) sqrt((double) n); for(int i = 0; i <= m; i++) { if(!sieve[i]) { for (int j = i*i; j <= n; j += i) sieve[j] = 1; } } return sieve; } int main() { eratosthenes(100000000); }
你能夠[把 Nim 編譯到 JavaScript], 而不是 C.
這樣你就能夠直接用 Nim 寫一些客戶端, 也能夠寫服務端.
咱們來寫一個服務端的訪問用戶統計, 在瀏覽器上顯示出來. 這是 client.nim
:
nimimport htmlgen, dom type Data = object visitors {.importc.}: int uniques {.importc.}: int ip {.importc.}: cstring proc printInfo(data: Data) {.exportc.} = var infoDiv = document.getElementById("info") infoDiv.innerHTML = p("You're visitor number ", $data.visitors, ", unique visitor number ", $data.uniques, " today. Your IP is ", $data.ip, ".")
咱們定義 Data
類型, 用來從服務器傳遞給客戶端.printInfo
程序會用 data
調用, 而後顯示.
使用 nim js client
編譯. 變異結果的 JavaScript 文件在 nimcache/client.js
.
對於服務器咱們須要用到 Nimble 包管理器而後運行 nimble install jester
.
以後咱們能夠用上 Jest Web 框架來寫 server.nim
:
nimimport jester, asyncdispatch, json, strutils, times, sets, htmlgen, strtabs var visitors = 0 uniques = initSet[string]() time: TimeInfo routes: get "/": resp body( `div`(id="info"), script(src="/client.js", `type`="text/javascript"), script(src="/visitors", `type`="text/javascript")) get "/client.js": const result = staticExec "nim -d:release js client" const clientJS = staticRead "nimcache/client.js" resp clientJS get "/visitors": let newTime = getTime().getLocalTime if newTime.monthDay != time.monthDay: visitors = 0 init uniques time = newTime inc visitors let ip = if request.headers.hasKey "X-Forwarded-For": request.headers["X-Forwarded-For"] else: request.ip uniques.incl ip let json = %{"visitors": %visitors, "uniques": %uniques.len, "ip": %ip} resp "printInfo($#)".format(json) runForever()
這個服務器就包含了主要的網頁. 一樣也包含了 client.js
, 用過在編譯時讀取編譯 client.nim
.
邏輯在 /visitors
當中處理.
用 nim -r c server
編譯運行, 打開 http://localhost:5000/ 查看效果.
你能夠在 Jester 生成的網站上看代碼執行效果, 或者下面內聯的:
內聯 HTML
我但願我能激發你對 Nim 語言的興趣.
注意這門語言尚未徹底穩定下來. 特別是一些含糊的功能你可能會遇到 bug.
可是好的一面是, Nim 1.0 計劃在將來 3 個月裏發佈! 因此如今開始學習 Nim 是很好的時機.
獎勵: 由於 Nim 編譯到 C 並且只依賴 C 標準庫, 你能夠在任何地方部署,
包括 x86-64, ARM 和 Intel Xeon Phi accelerator cards.
評論的話用 Reddit, Hacker News, 或者在 IRC(#nim on freenode) 上直接問 Nim 社區
你能夠經過個人我的郵件 dennis@felsin9.de 找到我.
感謝 Andreas Rumpf 和 Dominik Picheta 審閱這篇文章.