Nimのダウンロード
公式HPからどうぞ。
ドキュメントやフォーラムもここらへんにあります。
choosenimを使うと、Nimのバージョン管理が楽になりおすすめです。(解説記事)
インストールしている間、オンライン実行環境でお試しください。
バージョン
1.4.0での動作を確認しています。
基礎
言語の概観
Nimは "Efficient, Expressive, Elegant" を標榜するプログラミング言語です。
Effcient: 効率的
NimはC言語並みに処理速度が速いと巷に評判のコンパイル言語です。(なお、知名度がない)
また、構文がシンプルで親切なので、プログラミング自体が効率的です。
Expressive: 表現豊か
Nimは非常に自由度の高い言語です。
一例として、演算子(+とか-みたいなやつ)が自由に作れます。
import math
proc `$` (x, y: int): int = # 組み合わせ
return fac(x) div (fac(y) * fac(x - y))
echo 5 $ 2 # => 10
そのほか、関数型にもオブジェクト指向にも対応できる柔軟性を持ちます。
Elegant: 優雅
インデントを使うため、pythonと同じような見た目になります。賛否両論はあると思いますが、すっきりした見た目をしています。
メタプログラミングにも長けており、高いレベルの問題解決ができます。
ファイルのコンパイル・実行
nim c -r [ファイル名]
で実行してくれます。拡張子は.nim
がいいでしょう。
(コマンドラインのオプションの詳細)
これを実行すると、C言語(C++やjavascriptも可)を介してコンパイルし、実行されます。
サンプルコード
fizzbuzzのサンプルコードです。
今はわからなくても、この記事を読めば理解できるようになるでしょう。
from strutils import parseInt
echo "Input max Number: "
let maxNumber: int = readLine(stdin).parseInt
for i in 1..maxNumber:
if i mod 15 == 0: # 15の倍数
echo "fizzbuzz"
elif i mod 3 == 0: # 3の倍数
echo "fizz"
elif i mod 5 == 0: # 5の倍数
echo "buzz"
else:
echo $i
標準入力・標準出力
標準入力はreadLine(stdin)
で、標準出力はecho hoge
です。
以下、サンプルです。
echo "Your name? : "
var name: string = readLine(stdin)
echo "Hello ", name, "!"
コードブロック
pythonと同じく、インデントでブロックを表現しますが、スペース2個を使ってください。
TABを使うとエラーになります。
関数への引数渡し
実行する際の関数の書き方には、さまざまな方法があります。
文字列に文字を追加する関数add
で例を示します。
var welcome = "Welcome to"
add(welcome, ' ')
add welcome, 'N'
welcome.add 'i'
welcome.add('m')
welcome.add'!'
echo welcome
# => Welcome to Nim!
hogeの型がstring以外の場合、$
を頭につけてください。この$
は単に「文字列以外を文字列に変換する関数」で、他言語(Perlのsigil, Haskelのカッコなど)のような意味は持ちません。
コメント
#
を書くと、行の最後までコメントになります。
複数行のコメントには、#[ ]#
が使えます。
echo "Hello, World" #ハローワールド
#[
echo "お名前は? "
var name: string = readLine(stdin)
echo "やあ、", name, "!"
]#
宣言と代入
var x : int # 変数の宣言だけ
x = 0 # 代入だけ
var y : int = 1 # 宣言と代入
var z = 2 # 型が明らかなので省略
let w : int = 3 # 定数
# 同時に宣言や代入
var x, y = 3 # どちらもintの3になる
let
a = 1
b = 2
c = 3
定数にはconst
もありますがlet
との大きな違いとして、const
はコンパイル時に値が決まっている必要があります。例えば「標準入力を定数に格納したい」といった操作は、標準入力がコンパイル時にわからないのでlet
のみに格納できます。
# どちらもOK
let int_var = 3
const int_var = 3
# letのみ
let str_var = readLine(stdin)
const str_var = readLine(stdin) # コンパイルエラー
型
type(x)
としてください。
var x = 'a'
echo type(x) # => char
int/float/bool型
# 宣言
var
int_var : int # 整数型
flt_var : flaot # 浮動小数点型
bol_var : bool # ブール型
# 各種演算
## int同士の加減乗除
int_var = 1 + 1 - 2 * 3 # => -4
int_var += 1 # => -3
int_var = 21 div 13 # => 1 (整数の商)
int_var = 21 mod 13 # => 8 (余り)
## float同士の加減乗除
flt_var = 2.0 + 3.0 - 5.0 * 4.0 # => -15.0
flt_var = 5.0 / 2.0 # => 2.5 (intとの相違点)
## int同士の比較(floatも同様)
bol_var = 2 < 3 # => true # >, <=, >= も使える
bol_var = 2 == 4 # => false
bol_var = 2 != 3 # => true
## intとfloat同士も演算可能
flt_var = 2.0 - 3 * 5.2 # => -13.6
## bool同士の演算
bol_var = not true # => false
bol_var = true and false # => false # or, xorも使える
bol_var = (3 > 0) == true # => true
bol_var = false < true # => true
## 型変換
int_var = int(32.2 / 3) # => 10
int_var = toInt(32.2 / 3) # => 10
flt_var = float(32 div 3) # => 10
flt_var = toFloat(32 div 3) # => 10
ほとんど予想通りかと思いますが、以下の点に注意してください。
- floatの割り算は
/
ですが、intでの割り算はdiv
(商),mod
(余り)です。 - 累乗は
math
ライブラリにある、pow
あるいは^
(指数が自然数のときのみ)を使います。
また、int, floatの型にはいくつか種類があります。(詳細)
char/string型
# 宣言
var
chr_var : char # 文字型
str_var : string # 文字列型
int_var : int
bol_var : bool
chr_var = 'V' # リテラルは単引用符
str_var = "Variable" # リテラルは二重引用符
str_var = """long literal that can use " " too
and multiple lines"""
# `"""`で挟むとエスケープ不要で改行可能な文字リテラルが作れる。
# ASCII文字コードとの変換
var int_var = ord('A') # => 65
chr_var = chr(65) # => 'A'
# 文字列操作
str_var = "aa" & "bb" # => "aabb" # 連結
str_var = $1.0 # => "1.0" # 文字列へ変換
bol_var = 'a' in "abc" # => true # 文字が文字列に含まれる
bol_var = "ab" in "abc" # => true # 文字列が文字列に含まれる
int_var = len("abc") # => 3 # 文字数
より詳しい文字列操作は[標準ライブラリ](# 標準ライブラリ)を参照してください。
配列(コンテナ型)
配列のような型(コンテナ型)のうち重要なものは以下の5つあります。
- array型(固定長配列)
- seq型(可変長配列)
- set型(集合型)
- tuple型(タプル型)
- Table型(連想配列)
また、関数の引数として、
- openArray型
- varargs型
を使うことができます。
set型、tuple型、openArray型、varargs型に関しては、マニュアルを参照してください。
Table型に関しては、[標準ライブラリ](# 標準ライブラリ)を参照してください。
array型(固定長配列) / seq型(可変長配列)
# 宣言
var
int_arr: array[4, int] = [0, 3, 5, 2] # リテラルは`[]`
int_seq: seq[int] = @[5, 2, 8, 1] # リテラルは`@[]`
int_var : int
bol_var : bool
# for文で順番にアクセスできる
for value in int_arr: # int_seqも同様
echo value
# => 0
# => 3
# => 5
# => 2
# 基本的な配列操作
## array, seqに共通してできること
int_var = len(int_arr) # => 4 # 配列長
int_var = int_arr[2] # => 5 # 要素にアクセス
var int_subarr = int_arr[1..3] # => [3, 5, 2] # 部分列の取り出し
int_arr[0] = 4 # => int_arr <- [4, 3, 5, 2] # 要素を書き換え
int_var = max(int_arr) # => 5 # 最大値(最小値はmin)
bol_var = int_arr == [4, 3, 5, 2] # => true # 比較
## arrayからseqに変換
int_seq = @int_arr # => @[4, 3, 5, 2]
## arrayにできるが、seqにできないこと
# 特にないかな?
## seqにできるが、arrayにできないこと
int_seq = @[5, 2] & @[8, 1] # => @[5, 2, 8, 1] # 要素の連結
add(int_seq, 4) # int_seq <- @[5, 2, 8, 1, 4] # 要素の追加
add(int_seq, [1, 3]) # 配列の追加
# int_seq <- @[5, 2, 8, 1, 4, 1, 3]
del(int_seq, 1) # int_seq <- @[5, 8, 1, 4, 1, 3] # 要素の削除
上記コードのように、@
は固定長配列(array)の頭につけることで動的配列(seq)を意味します。
より詳しい配列操作は[標準ライブラリ](# 標準ライブラリ)を参照してください。
制御文
制御文には以下のものがあります。
- if-elif-else
- case-of-else
- while
- for
- try-except
前述した通り、ブロックはインデント(スペース2個)で表現します。
var int_var = 1
# if文
if (int_var == 1):
echo "value is 1"
elif (int_var > 1):
echo "value is greater than 1"
else:
echo "value is smaller than 1"
# case文
case (int_var)
of 1:
echo "value is 1"
of 2..100:
echo "value is greater than 1"
of -100..0:
echo "value is smaller than 1"
else:
echo "value is extreme"
# while文
while true:
int_var += 1
if int_var == 3:
continue
if int_var > 5:
break
echo $int_var
# => 1
# => 2
# => 4
# => 5
# for文 # continue, breakも使える
for i in 2..5: # 2..<6とも書ける
echo $i
# => 2
# => 3
# => 4
# => 5
# try文
echo "Please input:"
var str_var = readLine(stdin)
try:
echo str_var[100000]
except IndexDefect:
echo "line too short"
finally:
raise newException(ValueError, "you are doomed")
for文で配列のインデックスを使う場合
for i in 0..<len(arr):
ではなく
for i in low(arr)..high(arr):
for i, v in arr: # iにインデックス、vに値が入る
を使った方がいいでしょう。1
関数
関数2を定義するときはproc
を使います。
proc mean0(x: int, y: int, z = 2.0): float =
return float(x + y) / z
proc mean1(x, y: int, z: float = 2.0): float =
result = float(x + y) / z
proc no_return(x, y: int) =
echo $x & $y
echo mean0(1, 2) # => 1.5
echo mean0(1, 5, 3.0) # => 2.0
echo mean0(z = 1.0, x = 3, y = 2) # => 5.0
echo 3.mean0(3) # => 3.0 # わからなかったら「関数への引数渡し」を読んでね!
no_return(1, 2) # => 12
int_var = (proc(x, y: int): int = x + y)(1, 2) # => 3 # 無名関数
返り値は、return
文を使うほか、関数内であらかじめ定義されているresult
変数に代入する、一行で書けるなら=
につなげる、などで表現できます。
オーバーロード
Nimの関数はオーバーロードができます。
これはつまり、名前が同じ関数であっても引数の型が違えば、別の関数として定義できます。
proc twice(x: int): int = x * 2 # 一行ならこんな書き方もあるよ
# proc twice(x: int): int = ... # 既に同じ名前の関数があるのでエラー
proc twice(x: float): float = x * 2 # 型が違うならOK
ジェネリクス
オーバーロードとは逆に、いくつかの型に共通した処理をする関数を作りたい場合、ジェネリクスが使えます。ジェネリクスは関数の横の[]
で表現します。
例えば、上記のtwice(int)
とtwice(float)
は下の関数でまとめられます。
proc twice[T](x: T): T = x * 2
型の定義
ユーザーが新しく型を定義することができます。オブジェクト指向プログラミングと相性がいいでしょう。
type Character = object of RootObj # 継承されるには`of RootObj`が必要
name: string
hp: int
proc echoName(self: Character) = # メソッドは型を
echo self.name
type Player = object of Character # 継承
atk: int
let Hero = Player(name: "あああああ", hp: 10, atk: 2)
Hero.echoName # => "あああああ"
type
文はそのほか、特定の配列を定義したり、列挙型を定義するのにつかわれます。
type
Direction = enum
north, east, south, west
var dir_var: Direction = south
type
IntArray = array[5, int] # 長さ5, 型がintの固定長配列
var x: IntArray
x = [1, 2, 3, 4, 5, 6]
ファイル入出力
# ファイルを読む
var lines: seq[string] = @[]
block:
var f : File = open("read_from.txt" , FileMode.fmRead)
defer :
close(f)
while not f.endOfFile:
lines.add(f.readLine())
# ファイルに書く
block:
var f : File = open("write_to.txt" , FileMode.fmWrite)
defer:
close(f)
for line in lines:
f.writeLine(line)
block, deferに関しては、マニュアルを参照してください。
モジュール・ライブラリ
ほかのモジュールをインポートするにはimport
文を使います。
ほかのモジュールへエクスポートしたい変数、関数には*
をつけて置きましょう。
var
export_this* = 1
dont_export = 2
import module_a
echo export_this
echo dont_export # これはエラー
from module import hoge
を使うと、欲しいものだけをインポートできます。
標準ライブラリ
Nimは"battery included"で、標準ライブラリが充実しています。外部ライブラリはNimbleが使われます。choosenimを使ったら一緒に入っています。
ここでは、
- 数学: [math](## math), [random](## random)
- 文字列操作: [strutils](## strutils), [strformat](## strformat), [unicode](## unicode)
- 配列操作: [algorithm](## algorithm), [sequtils](## sequtils), [tables](## tables), [json (& marshal)](## json関連)
について扱います。
以下で使う変数を宣言しておきます。
var
chr_var : char
str_var : string
int_var : int
flt_var : float
bol_var : bool
str_seq : seq[string]
int_seq : seq[int]
math
import math
## 定数
echo PI # => 3.141592653589793
echo E # => 2.718281828459045
## 関数
int_var = sum([1, 2, 3]) # => 6 # 合計
int_var = fac(4) # => 24 # 階乗
int_var = 2.pow(5) # => 32 # 累乗
# そのほか、sin, log, roundなどがあります
random
import random
randomize() # seedのランダム化。やらないと以下の結果が同じになる。
int_var = rand(100) # => 0-100までの乱数
str_var = ["Ace", "King", "Queen", "Jack"]
shuffle(str_var) # str_varがランダムな順番になる
strutils
import strutils
# 文字列操作
str_seq = "ab,bc,cd".split(",") # => @["ab", "bc", "cd"] # 分割
str_var = "hello".repeat(3) # => "hellohello" # 繰り返し
bol_var = startsWith("Hello", "He") # => true # 開始文字
int_var = "Hello".count('l') # => 2 # カウント
str_var = "Hello".replace("lo", 'p') # => Help # 置き換え
# 型変換
int_var = "1".parseInt # => 1
flt_var = "2.0".parseFloat # => 1.0
# 文字列への変換は前述した`$`
# 文字の種類の判定
bol_var = isAlphaAscii('e') # => true # アルファベットか判定
bol_var = isDigit('8') # => true # 0-9か判定
# そのほか、大文字小文字やの判定・変換があります。
strformat
import strformat
flt_var = 2.0
str_var = &"---{flt_var + 1}---" # => "---3.0---" # 変数や計算
str_var = echo &"{flt_var = }" # => "flt_var = 2.0" # 変数の値の表示
str_var = &"{-12:05}" == " -0012" # 0埋め
str_var = &"{12.34:@>8.5f}" # => "@@12.340" # 小数点以下桁数、右寄せ、文字埋め
str_var = &"{12.0:!^16.3e}" # => "!!!1.200e+01!!!!" # 指数表記
unicode
import unicode
str_var = reversed("123456abc") # => "cba654321"
str_var = "日本語も使えるよ"
int_var = len(str_var) # => 24
# 非ASCII文字の長さは`len(string)`で取得できない
int_var = str_var.runeLen # => 8
str_seq = str_var.toRunes # => @["日", "本", "語", "も", "使", "え", "る", "よ"]
# 配列操作でできることができる(長さを取得したり、置換したり)
algorithm
import algorithm
str_seq = @["ab", "aa", "bc", "ba"]
str_seq.sort() # str_seq <- @["aa", "ab", "ba", "bc"]
str_seq.reverse() # str_seq <- @["bc", "ba", "ab", "aa"]
int_var = binarySearch(str_seq, "ba") # => 1
sequtils
import sequtils
int_var = "abracadabra".count('a') # => 5
int_seq = repeat(5, 3) # => @[5, 5, 5]
int_seq = cycle(@[1, 2], 3) # => @[1, 2, 1, 2, 1, 2]
int_seq = @[1, 2, 3].map(proc(x: int): float = x.toFloat)
# => @[1.0, 2.0, 3.0]
tables
Nimにはtablesをインポートしなくても{key0: value0, key1: value1}
という記法は存在しますが、これはあくまでも[(key0, value0), (key1, value1)]
と同等のシンタックスシュガーです。(()
はタプル)
hoge[key0]
という形でvalue0
にアクセスするには、Table
に変換する必要があります。
import tables
var int_str_table = {1: "one", 2: "two"}.toTable # 変換
int_str_table[3] = "three" # 代入
str_var = int_str_table[2] # => "two"
json
jsonを扱う場合、JsonNode
型を使います。これは文字列としてのjsonとNimのオブジェクトをつなぐ役割をします。
以下のコードでも説明しますが、模式図を載せます。
+--------+ %* +----------+ $ +--------+
| | -----> | | ------------> | |
| Object | | JsonNode | | json |
| | <----- | | <------------ | |
+--------+ to +----------+ parseJson +--------+
import json
type
User = object # 継承しないので`of RootObj`は不要
name, surname: string
age: int
var user_seq: seq[User] = @[
User(name: "Hatiman", surname: "Hikigaya", age: 17),
User(name: "Yukino", surname: "Yukinoshita", age: 16),
User(name: "Yui", surname: "Yuigahama", age: 17),
]
# オブジェクト -> JsonNode
var user_json_node: JsonNode = %* user_seq
echo user_json_node[0]["name"].getStr()
# => Hatiman
user_json_node[0]["name"] = %* "Hachiman"
# もっと複雑な型(ネストが深いなど)も代入可能
echo user_json_node[0]["name"].getStr()
# => Hachiman
# JsonNode -> json(の文字列)
str_var = $user_json_node
# => """[{"name":"Hachiman","surname":"Hikigaya","age":17},{"name":"Yukino","surname":"Yukinoshita","age":16},{"name":"Yui","surname":"Yuigahama","age":17}]"""
# $を使ってJsonNodeの文字列表現を取り出す。
str_var = """{"name":"Iroha","surname":"Issiki","age":16}"""
# json(の文字列)-> JsonNode
user_json_node = parseJson str_var
# JsonNode -> オブジェクト
var new_user : User = user_json_node.to(User)
user_seq.add(new_user)
# user_seq <-
# @[
# User(name: "Hachiman", surname: "Hikigaya", age: 17),
# User(name: "Yukino", surname: "Yukinoshita", age: 16),
# User(name: "Yui", surname: "Yuigahama", age: 17),
# User(name: "Iroha", surname: "Issiki", age: 16),
# ]
そのほかの機能
ここで紹介した基礎的な機能のほか、Nimには
- ポインタ
- Pragma
- メタプログラミング(Template, Macro)
- javascriptへの変換
- 多言語との連携
といった多くの機能があります。ご興味があればぜひ試してみてください。