Nim言語の設計思想とAI時代

プログラミング言語の歴史には、「速さか書きやすさか」という問いが繰り返し登場します。

Nimはそれを両立されるべく、Pythonのような構文でCと同等の速度を出すことを目標に、2005年からドイツのプログラマー、Andreas Rumpfによって開発されてきた言語です1

最初のコンパイラはPascalで書かれていましたが、現在はNim自身で書き直されており、コンパイラが自分自身をコンパイルできるという、いわゆるセルフホスティングの状態にあります。
もともと、AIブームを見越して設計されたわけではありませんが、「賢いコンパイラがあれば人間はPythonのように書き、マシンにはCコードを渡せばいい」という問いから出発した言語です。

関連記事

1. コードの見た目はPythonに近い

実際にコードを見ると、インデントでブロックを表現する点からPythonを彷彿とします。

# 手続き(関数)の定義
proc greet(name: string): string =
  result = "Hello, " & name & "!"

echo greet("Nim")Code language: PHP (php)

proc がNimでの関数定義キーワードです。
result という変数名は暗黙的に用意されており、最後に代入された値が戻り値になります。

型は静的で、コンパイル時に検査されます。
ただし型推論があるので、変数宣言時に常に型を書く必要はありません。

let numbers = @[1, 2, 3, 4, 5]   # seq[int] と推論される
var total = 0

for n in numbers:
  total += n

echo total  # 15Code language: PHP (php)

@[...] はNimの動的配列(seq)リテラルです。
Pythonのリストに相当します。

1.1. なぜCと同等の速度が出るのか(トランスパイル)

NimはC、C++、JavaScriptのいずれかにトランスパイルしてからコンパイルします2
直接バイナリを生成するのではなく、一度C言語に変換することで、GCCやClangが長年かけて磨いてきた最適化をそのまま受け取れます。

これはバイナリサイズにも影響します。
RustがLLVMを経由するのに対し、NimはCコンパイラが生成した小さなバイナリを出力できます3

また、FFI(外部関数インターフェース)はオーバーヘッドなしでC/C++ライブラリを呼び出せます。

# C標準ライブラリの関数をそのまま使う
proc printf(format: cstring): cint {.importc, varargs, header: "<stdio.h>".}

discard printf("Hello from C: %d\n", 42)Code language: PHP (php)

{.importc.} はコンパイラに「この関数はCから来る」と伝えるプラグマです。
ラッパーを別途書く必要がなく、C APIを直接宣言するだけで使えます。

1.2. メモリ管理は選べる(ORC)

Nimのメモリ管理方式はコンパイルフラグで切り替えられます4

# コンパイル時に指定する
nim c --mm:orc myapp.nim      # デフォルト(Nim 2.0以降)
nim c --mm:arc myapp.nim      # サイクル検出なしの参照カウンタ
nim c --mm:refc myapp.nim     # 従来のGC
nim c --mm:none myapp.nim     # 手動管理(組み込み向け)Code language: CSS (css)

Rustが所有権と借用規則でメモリ安全性を保証するのに対し、NimはデフォルトではORC(参照カウンタ+サイクル検出)を使います。
所有権モデルの厳格さではなく、学習の容易さや既存のPythonユーザーの移行しやすさを重視した設計です5

2. マクロで構文を拡張できる(DSL)

Nimのマクロは構文木(AST)を直接操作できます。
これにより、言語の構文を拡張してドメイン固有言語(DSL)を構築できます。

import macros

# 独自のループ構文をマクロで定義する例
macro times(n: int, body: untyped): untyped =
  result = newStmtList()
  for i in 0..<n.intVal:
    result.add(body)

3.times:
  echo "繰り返し"
# → "繰り返し" が3回出力されるCode language: PHP (php)

3.times: というブロック構文はNimの標準文法にはありませんが、マクロで定義することで自然なDSLとして機能します。
HappyXというフルスタックWebフレームワークもこの仕組みを活用しており、フロントエンドとバックエンドの両方をNimだけで記述できます。
バックエンドはそのまま実行し、フロントエンドのコードはJavaScriptにコンパイルします。

2.1. PythonとNimを共存させる(nimpy)

Pythonのコードベースを全部書き直すのは現実的ではありません。
そこで nimpy というライブラリを使えば、PythonからNimの関数を直接呼び出せます6

まずNim側でモジュールを書きます。

# fast_math.nim
import nimpy

proc sumSquares(arr: seq[float]): float {.exportpy.} =
  for x in arr:
    result += x * xCode language: PHP (php)

コンパイルして共有ライブラリを生成します。

nim c --app:lib --out:fast_math.so fast_math.nimCode language: CSS (css)

Pythonから呼び出します。

import fast_math

data = [1.0, 2.0, 3.0, 4.0, 5.0]
print(fast_math.sumSquares(data))  # 55.0Code language: PHP (php)

機械学習のパイプラインでボトルネックになっている部分だけをNimで書き直し、残りはPythonのまま運用できます。
PythonのC拡張を書くよりも構文がずっとシンプルになるのが売りなわけです。

3. Nimの注目のプロジェクト

エコシステムはまだ小さいですが、品質の高いプロジェクトは存在します。

TwitterのプライバシーフロントエンドであるNitter、NumPy/PyTorchに触発されたテンソルライブラリのArraymancer、日本人開発者が手がけているLaravel風フレームワークのBasolatoなどが代表例です。主要ライブラリのキュレーションリストは awesome-nim にまとまっています。

バージョン管理には choosenim というツールを使います。
RustのRustupに相当するもので、Nim自身がNimで書かれています。

# choosenim のインストールと使い方
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
choosenim 2.0.0      # バージョン指定でインストール
choosenim stable     # 安定版に切り替えCode language: PHP (php)

3.1. なぜ今、Nimなのか

AIインフラがPythonの処理速度に限界を感じ始めています。
ただ、Rustへの移行となると、なかなか大変。
その点、Nimはその中間に位置しながら、Rustほど学習コストが高くなく、Pythonのコードベースとも共存できます。

Stack Overflowの調査によれば、2023年時点でNimを使っている開発者は全体の0.38%にすぎません7
ニッチな言語ではありますが、20年前に立てた「賢いコンパイラが人間とマシンの橋渡しをする」という問いが、2020年代のAI開発環境でようやく現実的な需要と重なっているのが興味深いですね。

  1. 開発開始は2005年。2008年に「Nimrod」として公開され、2014年12月のv0.10.2リリースで現在の「Nim」に改名されました。 – Nim (programming language) – Wikipedia
  2. Nim 2.0時点で、C、C++、JavaScript、Objective-C、LLVMへのコンパイルをサポートしています。 – Nim (programming language) – Wikipedia
  3. バイナリサイズの比較はベンチマーク環境や実装内容に依存するため、一概に何分の一とは言えません。Rustフォーラムでも同様の議論があります。 – Comparison Rust vs Nim binary sizes for IOT applications – Nim Forum
  4. Nim 2.0でORCがデフォルトになりました。ORCはARCにサイクル検出を加えたもので、非同期処理で参照の循環が生じる場合に特に有効です。 – Nim v2.0 released – Nim Blog
  5. Nim 2.0以降、ORCはRustのような所有権モデルなしにメモリ安全性をある程度保証します。ただしRustのような静的な完全保証ではなく、サイクル検出は実行時に行われます。 – Introduction to ARC/ORC in Nim – Nim Blog
  6. nimpyはABIレベルの互換性を持ち、特定のPythonバージョンに依存しません。PythonのC拡張として動作します。 – nimpy – GitHub
  7. Stack Overflow Developer Survey 2023のデータ。ユーザー数は少ないが、使っている開発者からの満足度は高い傾向があります。 – Nim programming language version 2.0 released – DEVCLASS