Help us understand the problem. What is going on with this article?

nimのpragmaについて

皆さん初めまして。
アドベントカレンダー初参加です。ていうか、Qiita初投稿であります。技術も拙い素人学生ですが、以後お見知り置きを...

さて、今回はタイトルにもありますがnimのpragmaについて書こうと思います。nim固有の機能なのに出回っている情報が致命的に少ないので。

pragmaとは

pragmaはそもそもコンパイラに補足的に情報を与えるもので、C系の言語にある機能です。と言ってもnimの場合これらとはかなり様相が違っていて、C系のpragmaがコンパイラ毎に独自に存在している方言的な要素を持っているのに対し、nimはコンパイラ(トランスパイラ)が公式ののみなので実質言語の標準機能となります。
ユーザー定義も出来ます。強い。

import macros

macro tpub*(x: untyped): untyped =
  ## Marks a proc with an export asterisk when ``-d:testing`` is defined.
  expectKind(x, RoutineNodes)
  when defined(testing):
    let n = name(x)
    x.name = newTree(nnkPostfix, ident"*", n)
  result = x

proc main {.tpub.} =
  echo "test"

また、その機能もかなり広範に渡っており、キーワード並みの機能を持つpragmaや、コンパイルオプションを渡すもの、諸言語との連携を担当するものなどがあります。公式のマニュアルに記載されて居ないものもあり私も全てを理解しきれていない所がありますが、取り敢えず把握しているものを解説します。よくわかってない部分はほぼmanualの日本語訳です。

コア機能系

{.discardable.}

これを付けたprocは戻り値があっても代入せずに使うことができる(discardをつける必要がない)ようになります。戻り値があっても、と言うところがvoidとの違いです。

{.experimental.}

nim標準仕様にまだ組み込まれていない試験段階の諸機能を使えるようにするpragmaです。特定の機能だけ呼び出すこともできます。後述するpush,popで範囲を制限できます。

# これらは{.experimanetal.}でまとめて有効化できます。
{.experimental: "parallel".}
{.experimental: "callOperator".}
{.experimental: "codeReordering".}
# 他にもあるが割愛

proc hoge():void = echo "hoge"

# 並列処理がなされる
parallel:
    for i in 0..9:
        spawn hoge()

# pythonの__call__みたいな
type Fuga = object
proc `()`(self: Fuga): void = echo "fuga"

let fugaobj = Fuga()
fugaobj() # -> fuga
Fuga()() # -> fuga

# 後方参照できる
proc foo(): void = bar()
proc bar(): void = echo "bar"

foo() # -> bar 

{.global.}

proc内でこれを変数につけるとグローバル変数となります。

proc xorshift(): int =
    var
        x {.global.} = 123456789
        y {.global.} = 362436069
        z {.global.} = 521288629
        w {.global.} = 88675123
        t {.global.}: int

    t = x xor (x shl 11)
    x = y
    y = z
    z = w
    w = (w xor (w shr 19)) xor (t xor (t shr 8))
    return w

echo xorshift() # -> 252977563114
echo xorshift() # -> 646616338854

{.pure.}

enumの要素の帰属先を指定しないとエラーを出すようにします。

type MetaJP = enum
    hoge
    fuga

echo hoge # -> hoge

type MetaEN = enum {.pure.}
    foo
    bar

echo MetaEN.foo # -> foo(fooだけで指定するとエラー)

{.shallow.}

オブジェクトの浅いコピーをするようにします。

{.bycopy.}

オブジェクトを引数にした時値渡しするようにします。

{.byref.}

オブジェクトを引数にした時参照渡しするようにします。

{.register.}

Cのregisterキーワードに相当します。

{.push.},{.pop.}

宣言以降、プログラム全体に影響を及ぼすpragmaの範囲を制限することができます。

{.push experimental: "parallel".}
parallel:
    spawn echo "hoge"
    spawn echo "fuga"
    spawn echo "hego"
    spawn echo "higu"
{.pop.}
# これよりあとではparallel statementは使えない

オブジェクト指向系

{.final.}

これをもつtypeは継承することを禁止します。

{.inheritable.}

これをもつtypeはRootObjを継承しなくても継承できるようになります。

{.base.}

method定義時にオーバーライドされる関数につけます。

type
    Parent = object of RootObj
    Child = object of Parent

method fn(self: Parent) {.base.} =
    echo "I'm parent"
method fn(self: Child) =
    echo "I'm child"

Parent().fn() # -> I'm parent
Child().fn() # -> I'm child

高速化系

{.inline.}

procをインライン化します。ちょっとtemplateとの使い分けに悩みますが、templateの方が副作用絡みだと直感に反する挙動を起こします?

template echoTemp(a: int) =
    echo a
    echo a

proc plus1(a: int): int =
    echo "in: " & $a
    result = a + 1

echoTemp(plus1(1))
# -> in: 1
# 2
# in: 1
# 2

proc echoInline(a: int) {.inline.} =  
    echo a
    echo a

echoInline(plus1(1))
# -> in: 1
# 2
# 2

{.computedGoto.}

while true中のcaseをCソースでgotoに置き換える事で高速化するそうです。

type
  MyEnum = enum
    enumA, enumB, enumC, enumD, enumE

proc vm() =
  var instructions: array[0..100, MyEnum]
  instructions[2] = enumC
  instructions[3] = enumD
  instructions[4] = enumA
  instructions[5] = enumD
  instructions[6] = enumC
  instructions[7] = enumA
  instructions[8] = enumB

  instructions[12] = enumE
  var pc = 0
  while true:
    {.computedGoto.}
    let instr = instructions[pc]
    case instr
    of enumA:
      echo "yeah A"
    of enumC, enumD:
      echo "yeah CD"
    of enumB:
      echo "yeah B"
    of enumE:
      break
    inc(pc)

vm()

{.unroll.}

コードサイズを犠牲にしてループ展開をします。※現在では無視されるそうです。

{.linearScanEnd.}

caseのパターンマッチにおいて、あるパターンが起こる割合が高い場合それらのパターンに特化するように最適化するpragmaのようです。(手元で適当にコード書いて試してみましたがあんまり速度は変わらず...使い方が間違っている?)

メタプログラミング系

{.immediate.}

引数の評価を遅延させます。macroで使われるものでしたが、引数をuntypedにすることが推奨になりこのpragmaは非推奨になっています。

{.inject.}

templateの呼び出し元に識別子をアクセスすることを許可します。

template injectTest() =
    var x = 1

injectTest()
echo x # -> Error

template injectTest2() =
    var x {.inject.} = 1

injectTest2()
echo x # -> 1

{.gensym.}

templateの呼び出し元に識別子をアクセスすることを許可しないようにします。type, var, let, constはデフォルトで{.gensym.}が付き、それ以外は{.inject.}です。

{.dirty.}

スコープを突き抜けるデンジャラスなpragmaです。詳しく言うと呼び出し先に展開されます。ご利用は計画的に。

template hoge(): int =
    xx + 1

template fuga(x: int): int {.dirty.} =
    var xx = x
    hoge()


echo fuga(1) # -> 2

他言語連携系

{.importc.},{.header.}

Cの関数をnimの関数にバインドします。{.varargs.}を使うと可変長引数を取れます。

proc kbhit(): cint {.header: "<conio.h>", importc: "kbhit".}

while true:
    let isKbhit = if kbhit() == 0: false else: true
    echo isKbhit # -> true or false
    break

proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}

printf("Hello, %s", "work") # -> Hello, work

{.exportc.},{.importCpp.},{.importObjc.},{dynlib}...

これらについてはこちらに詳しく解説なさっている記事があり、そちらを見ていただいたほうがよいと思われるゆえ割愛します。
Nimで他の言語との連携方法まとめ

{.cdecl.},{.stdcall.},{.fastcall.},{.safecall.}

恐らくDLL関数の呼び出し規約の方式を指定するものだと思われます。

{.emit.}

C/C++コードを直接埋め込めます。

{.emit: """
static int cvariable = 420;
""".}

{.push stackTrace:off.}
proc embedsC() =
  var nimVar = 89
  # access Nim symbols within an emit section outside of string literals:
  {.emit: ["""fprintf(stdout, "%d\n", cvariable + (int)""", nimVar, ");"].}
{.pop.}

embedsC()

{.compile.}

C/C++コードをコンパイルしてリンクします。

{.link.}

オブジェクトファイルなどとのリンクができます。

when defined(gcc) and defined(windows):
    when defined(x86):
        {.link: "icon.res".}
    else:
        {.link: "icon.o".}

{.passC.}

Cコンパイラへパラメータを渡せます。

{.passL.}

リンカへパラメータを渡せるそうです。

明示系

{.noSideEffect.}

純粋関数であることを明示します。副作用を伴うとエラーになります。
nim0.19.0からfuncが純粋関数宣言のキーワードになったためもう使う理由はないです。

{.closure.}

クロージャであることを明示します。

{.procvar.}

procに渡すprocであることを明示すると思われます。

{.compileTime.}

コンパイル時のみに使われる関数ないし変数であることを明示します。

{.noReturn.}

procが戻り値を返さないことを明示します。

並列/平行系

{.thread.}

スレッド上で実行される関数であることを明示します。

{.async.}

これを付けると非同期関数になります。よって内部でawaitを使えます。

{.gcsafe.}

スレッドを使ってもGCが正しく作動することを明示します。
外の変数をいじる関数はこれを付けるとエラーになります。

エラーメッセージ系

{.error.}

エラーを出します。これもraiseとの使い分けに悩みますがコンパイルが続行される場合があるようです。公式には「オーバーロードや型変換のために有効な操作がいくつかあることを除外するために特に便利です」とかいてあります。

{.fatal.}

致命的エラーを出します。{.error.}と違ってコンパイルが停止することを保証します。

{.warning.}

警告を出します。コンパイルは続行されます。

{.hint.}

警告というほどでもない時に使うようです。コンパイル進捗を出力するためにnim compilerでも使われていますね。

{.raises.}

raiseされるエラーを明示します。

proc raiseIOorOS() {.raises: [IOError, OSError].} =
    if bo:
        raise newException(OSError, "OSError")
    raise newException(IOError, "IOError")

proc neverRaise() {.raises: [].} =
    echo "never raise"

neverRaise()
raiseIOorOS(true)

{.effects.}

raiseできるエラーをhintで出力するようですが、使いどころがよくわかりません。

proc hogehoge(bo: bool) =
  if bo:
      raise newException(IOError, "IO")
      {.effects.}
      # -> Hint: ref IOError [User]
  else:
      raise newException(OSError, "OS")


proc hoge(bo: bool) =
  if bo:
      raise newException(IOError, "IO")
  else:
      raise newException(OSError, "OS")
      {.effects.}
      # -> Hint: ref IOError [User]
      # -> Hint: ref OSError [User]

コンパイルオプション

{.check.}

コンパイル時チェックを行うか指定します。offにすると潜在的バグを通す可能性があります。

{.nilChecks.}

ぬるぽチェック

{.hints.}

offにするとhintが出なくなります。

{.optimization.}

最適化オプションを指定します。speedは時間計算量、sizeは空間計算量を最適化するようです。

{.deadCodeElim.}

到達不能コード、いわゆるデッドコードを除去します。

その他

{.pragma.}

pragmaを定義するpragmaです。

{.asyclic.}

循環するオブジェクトを生成する為に必要だそうです。

type Foo = ref object {.acyclic.}
  data: Foo
  id: int

{.volatile.}

volatileな変数を定義できるそうです。Cの概念だそうですが知りませんでした。

{.used.}

定義した関数を使用しなくても警告を出さないようにします。macro内部で使われる関数などで使うと便利です。

{.deprecated.}

関数が非推奨(廃止予定)であることを出力します。


Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away