Nimのおすすめ文献紹介
Nimのおすすめ文献の紹介です。日本語情報が少ないため英語のものが多めになります。
What is special about Nim? (Nimの特別なところは何か)
URL: https://hookrace.net/blog/what-is-special-about-nim (英語)
Nimのいいところを挙げた記事です。ひたすらにNimを褒めまくるという記事になっています。
Nim1.0が3か月以内にリリースされると書いてありますが、まだ0.15.2なのは注意です。(はやく1.0出てほしい・・・)
SDL2を使い、Nimで2Dのプラットフォーム・ゲームを書く
URL: http://postd.cc/writing-a-2d-platform-game-in-nim-with-sdl2/ (英語から日本語への翻訳)
原文: https://hookrace.net/blog/writing-a-2d-platform-game-in-nim-with-sdl2/
上記の What is special about Nim? と同じ方が書かれた記事です。こちらはPOSTDさんが日本語に翻訳されているようです。
SDLという最小限のメディアライブラリを使ってゲームを作るという記事です。
Nimでの基本的な書き方から発展に加え、配布向けのバイナリをビルドするという実践的な話題も扱っている網羅的な記事なので特におすすめです。普通のゲームプログラミング入門としての出来も素晴らしいのもいいですね。
Nim by Example (OOP Macro)
URL: https://nim-by-example.github.io/oop_macro/ (英語)
Nimのマクロを使い、C++風のclass構文を実現するという黒魔術面白い記事になっています。Nimのマクロの入門としてもおすすめです。
Go-lang like interface
URL: http://forum.nim-lang.org/t/2422#14994 (英語)
こちらはフォーラムのポストになります。内容は、Nimにはinterface無いけどマクロ使ってGo言語みたいなinterface作ったよ!的な記事です。
Nimは標準で言語機能が充実しているので実際に利用する機会は少ないかもしれませんが、Nimの強力さが分かりますね。
Nim binary size from 160 KB to 150 Bytes (Nimのバイナリサイズを160KBから150Bにする)
(追記分)
URL: https://hookrace.net/blog/nim-binary-size/(英語)
NimのバイナリサイズをLinux上で最小限にするという記事です。
まとめ
以上が現在おすすめの記事です。他にも面白い記事を発見したら追記したいと思います。
Nimの導入
以前のブログに描いたNimの記事がNim 入門
で検索するとトップにくるという想定外の事態になり、さらにNimのバージョンも進み記事の内容が古くなってしまったので、これはきちんと書き直さないといけないと思い、こちらのブログで導入記事を書き直すことにしました。
この記事はNimのバージョン0.15.2時点での記事です。おそらくしばらく経てばまた古くなると思います。その時はまた記事を更新しようと思います。
この記事ではWindowsを想定しています。Linux向けの記事はまたいずれ書きたいと思います。
必要なもの
Nimはコンパイルの際、基本的にCを通してバイナリにコンパイルするので、必然的にCのコンパイラが必要になります。
Nimで使うCコンパイラは様々なものがサポートされており、下記のものが公式にサポートされているようです。
- vcc (Microsoft Visual C++)
- gcc (GNU C Compiler)
- llvm_gcc (LLVM-GCC Compiler)
- icc (Intel C++ Compiler)
- clang (Clang Compiler)
- ucc (Generic UNIX C Compiler)
他のものが使える場合もあるそうですが、おそらく非推奨になるでしょう。
ここでは一番無難なGCCを導入します。
ダウンロード
Nimコンパイラ
Windowsの場合、Nim公式のdownloadページにあるZipsの32bitか64bitのどちらかを好みで選びましょう。
ここで重要なのが後述のCコンパイラとbitを合わせることです。32bitのNimコンパイラと64bitのCコンパイラを組み合わせると動かない場合があるので、合わせてダウンロードしましょう。
ここでは64bit版を導入します。
Zipsの下にはExesがありますが、 これはインストーラー形式でインストールができ、 同時にCコンパイラやAporia IDE(Nim用IDE)などもインストールできる優れものだったのですが、 0.15.0でバグが見つかり、メンテナンスも困難になっているようで、現在使うのは非推奨になっています。 とても便利なものだったので是非復活してほしいですね。
Cコンパイラ
GCCはWindowsの場合、おそらくTDM-GCCがいいと思います。 GCCは他にもmingw-w64からもダウンロードでき、Nim公式はこちらを推奨していますが、どれをダウンロードすればいいかわかりにくいのと、Msys2やCygwinなど今回必要なCコンパイラに加えて他の物もダウンロードしてきてしまうものが多いので、導入がしやすいTDM-GCCにしました。
TDM-GCCのDownloadページに行き、tdm64-gcc-[バージョン].exe
をダウンロードします。
インストール
Nimコンパイラはダウンロードしたzipファイルを任意のディレクトリに展開し、展開したディレクトリのbinディレクトリにPATHを通しましょう。
TDM-GCCはインストールの際に出てくる選択でMinGW-w64/TDM64を選んでインストールしましょう。ここではデフォルトのディレクトリ(C:/TDM-GCC-64)にインストールしたという前提でいきます。
設定
最後に設定です。このままではNimコンパイラにTDM-GCCの情報が伝わってないので、設定ファイルに追記します。
展開したNimコンパイラのディレクトリ(ここではnim-0.15.2
)のconfigディレクトリにあるnim.cfg
に
@if windows: gcc.path = r"C:\TDM-GCC-64\bin" @end
を追記します。
TDM-GCCのインストール場所を変えた場合は各自gcc.path
の変更をお願いします。
これでインストールは完了です。
使い方
インストールが上手くいったかの確認として、試しにコンパイルしてみます。
下記のコードをhelloworld.nim
というファイルに保存し、
# helloworld.nim echo "Hello World!!"
下記のコマンドを実行でハローワールドプログラムのコンパイルです。
nim c helloworld.nim
あとは
helloworld.exe
でHello World!!
が表示されれば確認完了です。
まとめ
バージョン0.15.2現在では結構インストールが面倒くさくなってしまっています。おそらく将来的(1.0が出るまで)には改善されると思うので期待して待ちましょう。
補足
Nimは0.15からwindowsでの端末出力がUTF-8になり、Windowsのコマンドプロンプトでは日本語を使った場合文字化けするようになってしまいました。
一時的な対策として、minttyをUTF-8で使うなどするしかないと思います。
おすすめはGitをインストールした際に付いてくるGit Bashをminttyで使うあたりでしょうか。
こちらもいずれ改善してほしいですね。
0.15.0ではUTF-8になってましたが、0.15.2からはもとに戻ったようです。
現在調査中です。
Nimのモジュール
Nimは普段のプログラミングに使える汎用のプログラミング言語ですが、
言語としてはシステムプログラミングを意識している言語です。
システムプログラミング言語としてはC/C++がメジャーな言語ですが、NimではC/C++と違って優秀なモジュールシステムを使うことができます。
今回はそんなモジュールの話です。
基本
基本的にはpython風の文法になっています。
import
Nimのモジュールシステムで他のモジュールを使うようにするには、
import モジュール名
と書くことによって使えます。
例えば、標準ライブラリでよく使うstrutilsをimportするには
import strutils
とすればすぐに使うことができます。
except
importする際に一部の関数などをインポートしないようにするexceptがあります。
import strutils except `%`, toUpper echo "$1" % "abc".toUpper # compile error!
from
他にも、特定の関数のみをインポートするfrom文があります。
from strutils import `%` echo "$1" % "abc" echo strutils.replace("abc", "a", "z") # モジュール名を含む場合使用可能
importはカンマで区切ることによって1行で複数モジュールをインポートすることができます。
import strutils, sequtils
さらに、asで別名にすることができます。
import strutils as su
インポートした関数などは、そのまま関数名で呼び出すことができ、strutils.format
のように呼び出すこともできます。
別のモジュール同士に同じ関数名が含まれている場合は、モジュール名をつけることによって区別することができます。
# modulea.nim proc echoInt*(a: int) = echo a
# moduleb.nim proc echoInt*(a: int) = echo a
import modulea import moduleb echoInt(1) # compile error! modulea.echoInt(1) # works! moduleb.echoInt(1) # works!
Nimのモジュールシステムではimport モジュール名
とした際に、
- 標準ライブラリ
- パッケージマネージャのnimbleによってインストールされたライブラリ
- 同ディレクトリ
からインポートしようとします。
インポートする際にはモジュール名に相対パスを使うことができ、相対パスの/
を.
に置き換えることもできます。
# modules/foo.nim proc echoInt*(a: int) = echo a
import modules.foo echoInt(1)
Asterisk(public)
サンプルの関数に付いているアスタリスク*
は関数を公開(public)するようにする文法です。
関数以外にもマクロや型などにも付けることができます。
export
他モジュールの関数などを別モジュールから公開するexportがあります。
# objmodule.nim type MyObject* = object
# basemodule.nim import objmodule export objmodule.MyObject proc `$`*(x: MyObject): string = "my object"
# main.nim import basemodule var x: MyObject echo $x
include
includeは他のファイルをそのまま取り込む文です。Cのプリプロセッサでいう#include <***.h>
ですね。
include fileA, fileB, fileC
使いどころとしては複数のAPIのwrapperを作るとき、コンパイル時に使用するAPIを切り替えできるようにする際にwhenと組み合わせて使ったりします。
const audioAPI = "wasapi" when audioAPI == "WASAPI": include wasapi.nim elif audioAPI == "OpenAL": include openal.nim elif audioAPI == "CoreAudio": include coreaudio.nim else: raise newException(Exception, "Unknown Audio API")
nimble
nimbleはNimのパッケージマネージャ兼ビルドシステムです。
C/C++と違い、公式で用意されているものなのでほとんどのNimライブラリがコマンド一発で入るというとても便利なものになっています。
$ nimble install csv
上記のコマンドでcsvパーサが一発で入ります。
後は、
import csv
を書いて直ぐに使い始めることができます。
nimbleで入れられるライブラリとして特徴的なのはcompilerライブラリです。
compilerライブラリは文字通りコンパイラのライブラリで、これはNimのコンパイラのライブラリになっています。
NimはコンパイラがNim自身で書かれているので(セルフホスティング)、こうしてコンパイラ自身をライブラリとして使えるようになっています。
まとめ
Nimのモジュールシステムは非常に優れており、これだけでC/C++に比べてアドバンテージがあるのではないでしょうか。
特にcompilerライブラリは他の言語ではなかなか無いライブラリで、とても興味深いのでいずれ単体で記事にしたいところです。
おまけ
NimではC/C++のようにコンパイル時にコンパイラに必要なファイル全てを渡す必要はなく、一つのファイルを渡せば後はimportから自動的に解析してコンパイルしてくれます。
さらにNimではインクリメンタルコンパイルができ、一度コンパイルしてしまえばあとは更新があった部分のみを再コンパイルしてくれるので2回目以降のコンパイルは非常に高速です。
1回目のコンパイルは標準ライブラリを含めてコンパイルされるので時間がかかりますが、2回目以降は高速化されるので実用上問題になることは少ないでしょう。
Nimではロードマップでインクリメンタルコンパイルの大幅な改善も予定されているのでさらに高速になることが予想されるので期待ですね!
Nimの関数について
Nimでは関数のオーバーロードができます。Nimでは独自の演算子が定義でき、オーバーロードができるので、演算子を活用したライブラリなども作れます。(例:パーサコンビネータなど)
それだけでなく、method call syntaxもあることによって、独自のライブラリでもNimらしい見た目で使うことができるので、とても読みやすいソースコードになります。(Nimは速度、生産性だけでなく読みやすさも重視しています)
以下はmethod call syntaxの例です。
# 両方共同じ意味 writeLine(stdout, "Hello!!") stdout.writeLine("Hello!!")
演算子
Nimで演算子は普通の関数定義で演算子をバッククォートで囲むことによって定義できます。
type MyInt = object value: int proc newMyInt(value: int): MyInt = return MyInt(value: value) proc `+`(left, right: MyInt): MyInt = return newMyInt(left.value + right.value) proc `$`(myint: MyInt): string = return $myint.value echo newMyInt(100) + newMyInt(50)
上記のコードは自作の型であるMyIntに新しい演算を定義しているコードです。
例
method call syntax、演算子オーバーロード、そして前回のdistinctを使うと新しい単位を定義するようなこともできます。
type Ether = distinct int proc `+`(left, right: Ether): Ether {.borrow.} # borrowは元の関数を借りてくる付加情報(プラグマ) proc `$`(ether: Ether): string {.borrow.} echo 50.Ether + 600.Ether
上記は新しい単位エーテルを定義している様子です。(ゲームとかに使うと面白そうですね)
まとめ
以上がNimの関数についてです。Nimの関数はDSL的に使うこともでき、とても読みやすいと思います。
DSLについてはNimの重要な機能であるtemplateやmacroと組み合わせて真価を発揮するものなので、それについてもいずれ記事を書きたいと思います。
型の定義
Nimの型定義についてです。
Nimには様々な型の定義方法があります。
型定義の種類として、
- enum
- object
- ref object
- ptr object
- 別名
- distinct
があります。
他にも型の特性を変えるものとして、not nil
があります。
定義方法
型定義の構文は、
type 型名 = 型定義の種類
です。
型定義の種類に上記のenumなどを記述します。
enum
enumは列挙型です。
NimのenumはC言語のenumと似ています。
type Direction = enum North East South West echo North
しかし、C言語と違うのはそれぞれに値を割り当てることができることです。
type Direction = enum North = "N" Eest = "E" South = "S" West = "W" echo North
{.pure.}
をつけることによって、どの列挙型に属すのかを記述するのを強制させることができます。
type Direction {.pure.} = enum North East South West echo North # error! echo Direction.North # works!
object
objectはいわゆる構造体で、デフォルトでスタック割当されます。
type GameObject = object x: int y: int var obj = GameObject(x: 1, y: 5) echo obj
ref object
ref objectはヒープ割当される構造体です。
type GameObject = ref object x: int y: int var obj = GameObject(x: 1, y: 5) echo obj.x, ":", obj.y
refとobjectは別のキーワードなので、既存の型のref版を作ることもできます。
type GameObject = object x: int y: int type PGameObject = ref GameObject var pobj = PGameObject(x: 1, y: 5) echo pobj.x, ":", pobj.y
object型とref型の両方で使える関数をor
を使うことで作ることもできます。
type GameObject = object x: int y: int type PGameObject = ref GameObject proc echoGameObject(obj: GameObject or PGameObject) = echo obj.x, ":", obj.y var obj = GameObject(x: 1, y: 5) var pobj = PGameObject(x: 2, y: 5) echoGameObject(obj) echoGameObject(pobj)
ptr object
ptr objectもヒープ割当される構造体ですが、GCによって追跡されません。よって通常はref objectを使うことが推奨されます。しかし、場合によっては低レベルまで制御して最適化することが求められるので、こういったものも用意されています。
type RawGameObject = ptr object x: int y: int var rawobj = cast[RawGameObject](alloc(sizeof(RawGameObject))) rawobj.x = 1 rawobj.y = 5 echo rawobj.x, ":", rawobj.y dealloc(rawobj)
Nimはシステムプログラミングを明確に意識しているので、こういった低レベル操作もできるようになっています。
ジェネリクス
type Buffer[T] = ref object data: seq[T] var buf = Buffer[int](data: @[1, 2, 3, 4, 5]) echo buf.data
NimのジェネリクスはC++のようにテンプレート系のジェネリクスです。そのためエラーメッセージが読みにくいという欠点があるので注意です。
別名
型に別名を付ける方法です。おそらくあまり使うことは無いと思いますが一応。
type IntSeq = seq[int] proc echoIntSeq(data: seq[int]) = echo data var a: IntSeq = @[1, 2, 3, 4, 5] var b: seq[int] = @[1, 2, 3, 4, 5] echoIntSeq(a) echoIntSeq(b)
あくまで別名を付けるだけなので、元の型と同じものとして扱うことができます。
distinct
distinctは既存の型から新しい型を作ります。なので、上記の別名とは違い、同じものとして扱うことはできません。
type Index = distinct int var a = 1 var b: Index = 2 # error! var b: Index = Index(2) # works!
not nil
not nilはその型がnilになることを防ぐ機能です。具体的には、objectが初期化されずに生成されることを防いだりするのに使います。
type GameObject = object x: int y: int type PGameObject = ref GameObject not nil var pobj: PGameObject # error! var pobjinit: PGameObject = PGameObject(x: 1, y: 5) # works!
まとめ
Nimの型は様々な定義方法があります。ゆるい型付けとしても扱えますし、堅めの型としても扱えます。それを柔軟と言うか一貫性が無いと言うかは人によると思いますが、面白い型の扱い方だと思います。
Nimのデータ型
Nimのデータ型についてです。
基本的なデータ型
Nimの基本的なデータ型としては、
- int
- int8
- int16
- int32
- int64
- uint
- uint8
- uint16
- uint32
- uint64
- float
- float32
- float64
- bool
- char
- string
- cstring
などがあります。 先頭にuがついているものはCでいうunsignedで、末尾の数字はビット数を表しています。
コンテナ型
そしてそれらを包含するコンテナ型があります。Nimのコンテナ型はそれぞれが使いやすく、演算子オーバーロードがあり、シンタックスシュガーが用意されているのでとても書きやすいです。
コンテナ型でよく使うものとしては、
- array
- seq
- varargs
- openarray
- Table
ちなみに下記の例では分かりやすさのため変数に型を明示的に付けているものもありますが、この程度ならNimのコンパイラは型推論してくれるので省略可能です。
array
arrayは静的配列です。
var arr: array[5, int] = [1, 2, 3, 4, 5]
seq
seqは動的配列です。@をつけることで作成可能です。
var arr: seq[int] = @[1, 2, 3, 4, 5]
varargs
varargsは可変長引数に使います。 ちなみに@をつけることでseqに変換可能です。
proc echoVariadic(args: varargs[int]) = echo @args echoVariadic(1, 2, 3, 4, 5)
さらに、varargsで可変長引数を受け取る際に、任意の変換処理を行うことができます。
proc toInt(value: string): int = case value of "1": return 1 of "2": return 2 else: return 0 proc echoVariadic(args: varargs[int, toInt]) = echo @args echoVariadic(1, 2, "1", "2")
openarray
openarrayは配列を汎用的に扱う型です。
例えば、arrayとseq両方で使える関数を作ったりするのに使えます。
proc echoHead(arr: openarray[int]) = echo arr[0] echoHead([1, 2, 3, 4, 5]) echoHead(@[1, 2, 3, 4, 5])
Table
Tableはハッシュテーブルで、標準ライブラリtablesをimportすることで使えます。
他にも順序が付いたOrderedTableなどもあります。
var agetable = initTable[string, int]() agetable["genji"] = 35 agetable["hanzo"] = 38 agetable["mercy"] = 37 echo agetable["genji"]
ポインタ型
- pointer
- ptr
- ref
pointer
pointerは生ポインタ型で、Cでいうvoid*です。基本的にはCとの連携の際に使うもので、Nimに閉じたプログラムを書く際に必要になることは無いと思います。
ptr
ptrは型がついたpointerです。これも基本的にはCとの連携の際に使うことが多いでしょう。
ref
refも型がついたpointerなのですが、ptrとrefはGCで管理されるかどうかの違いがあります。
ptrはGCで管理されず、refはGCで管理されます。
まとめ
以上がNimのデータ型でよく使うものです。ただし後半のポインタ型はデータ定義のobjectなどと密接に関係してくるもので、少し分かりにくい説明になったと思います。データ定義などの部分はまた別の記事で書きたいと思います。
Nimの基本構文
まず、他の言語にもあるような基本的な構文を紹介していきます。
Hello World
まずは最初の一歩のHelloWorldです。
echo "Hello World!!"
これは、
echo("Hello World!!")
これと同じ意味です。
Nimでは、rubyのように関数呼び出しの場合に括弧を省略することができます。しかし、場合によっては括弧を付ける必要があるのでそこは注意です。
変数、定数、コンパイル時定数
それぞれ、var(変数)、let(定数)、const(コンパイル時定数)になります。
var name = "Hello!!" echo name name = "Rewrite!!" echo name
let name = "Hello!!" echo name
const name = "Hello!!" echo name
値が明示の場合、型推論がされます。しかし、値が無い(Nimの場合はnil
)場合、型宣言が必要です。
var name: string name = "Hello!!"
条件分岐
構文としてはPythonのようなif elif else
です。
真偽値はtrue bool
です。
if true: echo "True!!" else: echo "False!!"
Nimは構造を表現する文法として、Pythonのようなインデント形式を採用しています。基本的にはスペース2つで表現します。
他にも、case of
構文があり、
var name = "hello" case "hello" of "hello": echo 1 of "world": echo 2 else: echo 3
のように書けます。
他にもwhenがありますが、これはコンパイル時分岐です。
繰り返し構文
繰り返し構文は、for while
があります。
for i in 1..5: echo i
var i = 0 while i < 5: echo i i += 1
for whileの両方でbreak continue
が使えます。
..
はいわゆるRangeです。
配列をループする際にも、
var arr = [1, 2, 3, 4, 5] for val in arr: echo val
このように描けます。
indexで回すこともできます。
var arr = [1, 2, 3, 4, 5] for i in 0..arr.len-1: echo arr[i]
しかし、この場合には..<
演算子で、
var arr = [1, 2, 3, 4, 5] for i in 0..<arr.len: echo arr[i]
のように書くこともできます。
関数
関数はNimでは手続きと言われており、構文としてはproc
になります。
proc add5(val: int): int = return val + 5
関数宣言では型の省略はできません。
returnで戻り値を返していますが、他に戻り値を返す方法として、resultに代入するという方法もあります。
proc add5(val: int): int = result = val + 5
まとめ
以上がNimの基本構文になります。しかしNimはその他の機能が豊富なのでそれはまた別の記事に描きたいと思います。