Nim
プログラミング言語
More than 1 year has passed since last update.

Nim言語感想&概説

Nim言語を少しやってみた感じの感想&概説です。Nimの日本語での情報は少ないので書いてみた次第です。

他のほとんどの言語にもあるような基本的な構文などについては省略します。

構文については、他の方が書かれたSyntax of Nim (Gist)が参考になると思います。

公式: http://nim-lang.org/

追記
この記事は0.14.2時点の情報です。

Nimの特徴

  • ネイティブコンパイル
  • 静的型付けで型推論あり
  • ジェネリクス
  • GCあり
  • Python+Ruby風構文
  • 強力なメタプログラミング機構

簡単なサンプルコードです。

proc fib(n: int): int =
  if n < 2:
    result = n
  else:
    result = fib(n - 1) + fib(n - 2)

echo fib(38)

「Nim言語」で検索して最初に出てくる記事で目を引くのはベンチマークで、Go、Rustを抜かしてC並の速度が出るというなかなかにやばいことが書いてあったりします。(CのコンパイラオプションがO2の場合はNimの方が速い)

NimはCを通してネイティブコンパイルする言語なので(他にもJSなどにも変換可能)Cの最適化の恩恵を受けることができるとはいえ、これはかなりすごいことだと思います。

実際に自分の環境でフィボナッチ数列のベンチマークとってみましたが、本当にCと同等の速度が出たので驚きました。(さらにいうと-O3でコンパイルしたCよりもNimの方がごく僅かに早かった)

目を引くのは実行速度だけでなく、コンパイルして生成された実行ファイルの小ささも魅力的でした。Cよりは少しでかいものの(当たり前ですが)、他のネイティブコンパイルする言語に比べてかなり小さいという結果が出ました。

Nimの適用領域

Nimは普段のスクリプトとしてはもちろんWebサーバーなどにも使える万能言語です。さらにNimが意識している領域としてシステムプログラミングがあり、それに適した機能などが色々あったりします。

システムプログラミング向けの機能として、

  • 手動メモリ管理
  • Cとの連携
  • ソフトリアルタイムシステム向けのGC
  • ゼロオーバーヘッドのイテレータ

一応公式にはインラインによってvtableを使わない動的なメソッドバインドができるみたいなことが書いてあるのですが、これに関しては詳しくないので省略します。

GCについて

NimのGCはCycle Collectorといって、基本的には参照カウント(Nimの場合遅延参照カウント)でメモリを管理しますが、これだけでは循環参照が解放できないので時々Mark&Sweepで循環参照を解放するという方式です。(PHPやPython、他にはレンダリングエンジンのGeckoなどで採用されているようです。)

GCの制御などもできるようになっています。

追記
遅延参照カウントについて追記しました。

詳しくは: http://nim-lang.org/docs/gc.html

影響を受けた言語

Ada, Modula-3, Lisp, C++, Object Pascal, Python, Oberon

個人的にはLispの影響がでかいと思いました。さしずめ静的なCommonLispって感じです。あと時々Pascalっぽいところがあったり。構文はPythonっぽくもRubyっぽくもあります。

面白い機能

  • not nil
  • defer
  • region
  • pragma
  • ASTベースのオーバーロード
  • 項書き換えマクロ
  • ユーザー定義演算子

not nilは特定の型に関して強制的にnilを許容しない(変数の初期化を強制するなど)ようにする機能です。

type
  PObject = ref RootObj not nil

proc p(x: PObject) =
  echo "not nil"

p(nil) # Compile Error!

var x: PObject # Compile Error!
p(x)

deferはGoにもある通り、最終処理を書きやすくする機能です。

proc writeNeptune() =
  var f = open("neptune.txt", mode=FileMode.fmWrite)
  defer: close(f)
  f.writeLine "purple"
  f.writeLine "black"

writeNeptune()

regionは生ポインタを領域ごとに型付けするという機能らしいです。いまいち分かりにくいのと使いどころに悩む機能です。

pragmaは基本的にはコンパイラへのメッセージで、最適化や付加情報を付けたりなどに使ったりします。わかりやすい例としてはforと一緒に使用するunrollプラグマがあったりします。ループアンローリングでの最適化を便利な形で提供するあたり、最適化に本気を出しているといえるでしょう。

proc searchChar(s: string, c: char): int =
  for i in 0..s.high:
    {.unroll: 4.}
    if s[i] == c: return i
  result = -1

ASTベースのオーバーロードは引数がconstかどうかなどによって関数を切り替えたりする機能です。

proc optLit(a: string{lit|`const`}) =
  echo "string literal"
proc optLit(a: string) =
  echo "no string literal"

const constant = "abc"
var variable = "xyz"

optLit("literal") # output: string literal
optLit(constant) # output: string literal
optLit(variable) # output: no string literal

項書き換えマクロは関数呼び出しの特定のケースに代わりに呼び出されるマクロです。最適化に使ったりします。CommonLispでいうとコンパイラマクロでしょうか。

template optMul{`*`(a, 2)}(a: int) : int = a+a

let x = 3
echo x * 2

ユーザー定義演算子は文字通り演算子を自由に定義できる機能です。

proc `<<`*(left: string, right: string) =
  echo left
  echo right

"hello" << "konitiwa"

こちら (http://nim-lang.org/docs/manual.html) を見れば他にもユニークな機能がいっぱいあったりします。

メタプログラミング

メタプログラミングに関する機能として、

  • template
  • macro

があります。

template

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, "Rabbit.txt", fmWrite):
  txt.writeLine("BlueMountain")
  txt.writeLine("Mocha")

templateで注目してもらいたいのが、Nimの構文に即した形で書けるようになっているということです。

インデントによるブロックを受け取ることができるのと、RustやJuliaと違い、テンプレートやマクロの呼び出しが関数呼び出しと同じ構文になっています。

macro

マクロに関してのコードは長くなるので拙作のコードのGistへのリンクを貼っておきます。
Gist: hsssnow23/derive_macro.nim

追記: マクロに関しては別に記事を書きました。
Qiita: Nimでメタプロ(JSONシリアライズ)

マクロは単純に構文木を受け取り構文木を返す関数です。しかしLispのマクロはリストを操作しますが、Nimは言語の構文木を操作します。つまり少し書きにくいということです。しかしマクロを書く際に便利な関数など(quoteなど)があり、慣れれば割りと書けます。(Rustのコンパイラプラグインよりは圧倒的に書きやすい)

周辺ツール

  • nimble (パッケージマネージャ兼ビルドシステム)
  • Aporia (Nim製IDE)
  • NimScript
  • nimsuggest (補完用の汎用ツール)
  • c2nim

周辺ツールは公式で色々用意されてたりします。
特筆したいのがNimScriptで、VMでのNim実装なのですが、nimbleの設定ファイルとして採用されていたり、RubyのRakeのようなDSLでビルドツール的なことができたりと結構面白いです。(Nimのコンパイル時評価もこのVMで実行しているようです)

c2nimも面白く、Cのバインディングを自動生成するツールなのですが、プリプロセッサもNimに翻訳してくれるという優れものです。

追記

コメントでnimsuggestは補完だけでなく、lint、syntax highlight、ファイルのoutline出力が可能との指摘をいただきました。他にもnimscriptで設定ができるようです。ご指摘ありがとうございました。

欠点

型推論がRustほど賢くない、そのままパースできそうなところで括弧が必要だったりと一応言語としての欠点はあったりします。(なにか事情があるのかもしれませんが)

あと、ドキュメントが圧倒的に少ないです。公式では結構色々書いてあるのですが、やはりそれだけでは足らず、真面目に使おうと思うと結構つらいです。あとコンパイラのバグっぽいのをちょくちょく踏み抜きます。現状は実用しようと思うとつらいところが多いと思います。ただしこれは言語の欠陥というよりは単に開発リソース不足とコミュニティの規模がでかくないことに由来しているので今後どうにかなる可能性はあります。

追記

Nimの処理系は既にセルフホスティングされているので、コンパイラ自身が貴重な深く掘り下げたドキュメント代わりになったりします。

さらに最近はコンパイラもかなり成熟しているようで、バグを踏み抜くことはかなり少なくなってきたように感じます。

今後

最近Nimは公式でアンケートが行われ、ドキュメントが不足していると感じている人が多いと分かる結果になりました。その他コンパイラの安定化などを望む声が多く、これからVer1.0に向けてコンパイラのクオリティを上げ、より良い実用言語を目指していく方向になると思います。

明るい未来について話しておくと、アンケートでNimを仕事に使いたいという人が50%を超えたり(層が偏ってるとは思いますが)、Nimのコア開発者の方がNim in Actionという本を執筆していたりと熱心な人は相当熱心みたいです。

まとめ

C言語並の速度が出て、Lisp級のマクロがあり、最適化のための機能も多いのでもしかしたらあらゆるプログラミング言語の中でも(Cを抜かして)最速を狙っていける可能性のある言語だと個人的には思いました。

言語としては書きやすく、速度が出て、かつ柔軟というとても良い言語です。しかし、競合であるRustやD、Goに比べて知名度が低く、日本で知っている人はどれぐらいいるのかという状況です。

書いていてとても楽しい言語で、おそらく気に入る人は狂信者になるくらい気に入る言語だと思うので、この記事でNimが気になった人は是非少しでもやってみてもらえたら幸いです。

参考文献