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

PUBLISHED ON APR 22, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    模版 (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