Nim 語言程式教學:模板 (Template)

模版 (template) 是一種較為高階的語法特性,透過程式碼代換來改寫程式,好處是可以跳脫型別的限制。模版有點類似於 C 的前置處理器 (preprocessor),Nim 編譯器會將使用到模版的程式碼代換掉,然後繼續編譯程式。Nim 的模版也是用 Nim 語言撰寫,程式設計者不需額外學習新的語法。

撰寫模版

見以下例子:

template `!=` (a, b: untyped): untyped =
  not (a == b)

assert(5 != 6)

在這個例子中,5 != 6 會代換為 not (5 == 6)。在 Nim 裡面,有些語法其實是用模板製作的。

Untyped vs. Typed

在撰寫模版時,可指定變數為 untypedtyped,也可以指定為特定型別,型別的指定會有一些微妙的差別。以下的程式可以順利執行:

template declareInt(x: untyped) =
  var x: int

declareInt(x) # valid
x = 3

但是以下的程式會引發錯誤:

template declareInt(x: typed) =
  var x: int

declareInt(x) # invalid, because x has not been declared and so has no type

傳遞區塊

除了傳遞單一變數,模板也可以傳遞程式碼區塊,撰寫模板時,最後一個參數可視為程式碼區塊;在呼叫該模版時,加入 : (引號) 即可。如下例:

template withFile(f, fn, mode, actions: untyped): untyped =
  var f: File
  if open(f, fn, mode):
    try:
      actions
    finally:
      close(f)
  else:
    quit("cannot open: " & fn)

withFile(txt, "ttempl3.txt", fmWrite):
  txt.writeLine("line 1")
  txt.writeLine("line 2")

Hygienic

Nim 的模板是衛生的 (hygienic),意指 Nim 的模板在代換的過程中不會將新的變數置入目前的模板中。

我們現在寫了一個模組 a:

# module a: a.nim
var
  lastId = 0

template genId*: untyped =
  inc(lastId)
  lastId

我們引用模組 a 時,lastId 不會置入目前的模組中:

# main module: main.nim
import a

echo genId()

# Error
echo lastId

建立識別字

模板可以用反引號建立識別字。如下例:

template typedef(name: untyped, typ: typedesc) =
  type
    `T name`* {.inject.} = typ
    `P name`* {.inject.} = ref `T name`

typedef(myint, int)
var x: PMyInt
comments powered by Disqus