本稿ではNim言語 (旧称: Nimrod) の構文を紹介します。
Nim言語の概要やインストール方法については、ブログ記事があるのでそちらをご参照下さい。
筆者はNimの開発者ではなく、本稿も公式なドキュメントではありません。本稿は筆者がNimのチュートリアルを読みながら書いたメモ書きを基にしており、内容には間違いが含まれている可能性があります。Nimは正式リリース前であり、今後仕様が変更される可能性があります。したがって、本稿の内容と実際の仕様・実装と異なる可能性もあります。間違いや仕様変更への遅れなどがありましたらご指摘いただけると幸いです。
以下の資料を参考にしていますが、翻訳ではありません。また、サンプルコードをそのまま、または一部改変して使用させて頂いております。問題がある場合はご指摘いただければ幸いです。
- 参考
- Nim言語のマニュアル(Nim Manual)
- 公式のチュートリアル(Part 1, Part II)
- Nim by Example
- Nim Standard Library
文字列(string
型)はダブルクォート"
で囲んで表現します。
シングルクォート'
で囲んでも文字列にはならないので注意して下さい。
文字列は、echo
文または echo()
関数で標準出力に表示できます。
echo("hello") # >>> hello
echo "hello" # >>> hello
echo('hello') # これはコンパイル時エラー!!!
ざっと見た限りでは、echo
は文と関数どちらの表記を推奨しているのか判断つきませんでした。
マニュアルでも両方使われている(文の方が多く、関数形式はif文やcase文の中が多い?)ので、case-by-caseでいいのでは無いでしょうか。
関数形式の方が型変換や演算順序などで混乱が起きにくい気がしました。
また、Pythonなどと同様に、三重のダブルクォート(""" ... """
)で囲むことで、改行を含む文字列を表現することができます。
echo("""Output
multiple
lines""")
# >>> Output
# multiple
# lines
#
標準入力の取得はreadLine(stdin)
でできます。
チュートリアルのコードを引用し、ついでにマルチバイト文字にしてみました。
# これはコメントです
echo("あなたのお名前は?")
var name: string = readLine(stdin)
echo("こんにちは、", name, "さん!")
このコードをgreeting.nim
に書いてnim c -r greeting.nim
すると
あなたのお名前は?
みやこ
こんにちは、みやこさん!
という感じで心安らぐ挨拶が交わせます
当然ですが、ファイルはUTF-8で保存してください。Windowsのコマンドプロンプトで正常に動作するかは確認していません。もし上手く動かなくて修正が必要になった時、中間のCのコードが存在するのは便利ですねだぶん。
最後の行はecho文で書くこともできます。
以下の例では、&
演算子を使い文字列を連結してから表示しています。
echo "こんにちは、" & name & "さん!"
三種類の宣言があります。
変更可能 (mutable)な変数を宣言します。 型を宣言する必要がありますが、同時に代入を行って型が推論可能な場合は型の指定を省略できます。
var i: int # int型の変数の宣言、0で初期化される
echo i # >>> 0
i = 10 # 違う値(int型)を代入
echo i # >>> 10
var x = 1 # 型推論でint型と判定、1で初期化
echo x # >>> 1
x = 2 # OK
echo x # >>> 2
i = "a" # 異なる型(string)の代入なのでエラー
x = 1.1 # 同じく型が違う(float)のでエラー
複数行でまとめて宣言・初期化することも可能です。 ブロックはインデント(2スペース)で表されます。
var
a, b: int # aとbを同時にint型として宣言
c, d = 2, 3 # cとdに同時に代入
この記法は次に述べる const
, let
でも可能です。
変更不可な定数です。宣言時に初期値を与える必要があり、型だけ指定した宣言(const i: int
など)はエラーになります。
const C = 1 # OK
echo("C=", C) # >>> C=C
const I: int # 初期値が与えられていないのでエラー
これもconst
と同じく変更できません。宣言時に初期値を与える必要もあります。しかし、const
とは違ってコンパイル時に値が決定されている必要はありません。
let l = 1 # OK
echo l # >>> 1
l = 3 # 値の変更はできないのでエラー
let m: int # 初期値が与えられていないのでエラー
let
とconst
の違いはコンパイル時に決定されるか否かです。
公式マニュアルの標準入力を使ったletの例がわかりやすかったので引用します。
const input = readLine(stdin) # Error: constant expression expected
let input = readLine(stdin) # works
構文はPythonと同じで、行末にはコロンが必要です。
ブロックはインデント(2スペース)で表します。
各ブロックはインデントの深さで区別されます。
真偽値は小文字でtrue
/false
です。
let a = 1
if a == 0:
echo "ゼロ"
elif a == 1:
echo "いち"
else:
echo("なにこれ→ ", a)
Pythonには無いcase文。
私はPythonでcase文がほしいと思ったことはあまりありません(elifをたくさん書くのと違いがわからないのです)が、同じ変数に対してelif
を繰り返すよりも変数 ==
を書かずにすむので便利かもしれません。
if文のコードをcase文で書き直します。
let b = 1
case b
of 0:
echo "ゼロ"
of 1:
echo "いち"
else:
echo("なにこれ→ ", b)
上記のコードからわかるように、某スクリプト言語のswitch文のようなエグい振る舞いはしないので、毎回break
を書いたりする必要はありません。
真偽値(bool
)はtrue
/false
の二値です。
否定はnot
で、and, or, xorはそのままand
, or
, xor
です。
bool
への暗黙の型変換は行われないので、bool
以外の型("true", 0, nil
など)を条件に使うとコンパイル時エラーになります。
if
の代わりにwhen
もあります。
when-elif-else
で構文はif
を使った場合と同じです。
ただし、各条件式はコンパイル時評価が可能でなくてはなりません。
要するにC言語の#ifdef
のような感じで、プラットフォーム毎に異なるコード(例えばWindowsとLinuxで処理を分けるとか)を書きたい時にはif
よりも便利だと思います。
また、whin false:
でブロックをまるごとコメントアウトのようなことができて便利とのことです。
while
, for
ともに構文はPythonと同じです。
var a = 0
while a < 10:
a = a + 1
echo a
# (1 ~ 10まで一行ずつ出力される)
break
やcontinue
も使えます。
var b = 0
while b < 10:
b = b + 1
if b == 5:
continue
if b == 7:
break
echo b
# (1, 2, 3, 4, 6 と一行ずつ出力される)
先ほとのwhileのコードをforループで書き直してみます。
for i in countup(1, 10):
echo(i)
# (1 ~ 10まで一行ずつ出力される)
countup
は第一引数と第二引数に開始値と終了値を入れます。
第二引数の方が小さい場合はエラーになるので、countdown
関数を使いましょう。
どちらの関数も第三引数でstepサイズを与える事もできます。
返す値はiteratorなので、Pythonのrangeにそっくりですね(当然ここでのPythonも3.0以上のことを指しています)。
countup(1, 10)
の部分は1..10
と書くこともできます。また、while
同様continue
やbreak
によるフロー制御も可能です。
for b in 1..10:
if b == 5:
continue
if b == 7:
break
echo b
# (1, 2, 3, 4, 6 と一行ずつ出力される)
in
の後にはとりあえずiteratorを置いておけばいいようですが、iterator周りはまだよく理解できていません。
for
, while
のブロック内は外側と別スコープです。
内側から外側の変数を参照することはできますが、逆はできません。
# OK
var a = "a"
for c in 1..2:
echo a
# Error
for c in 1..2:
var d = "d"
echo d # Error!
for
やwhile
や関数を使わなくても、block
でもブロックを作ることができ、スコープを分離できます。
したがって、以下のコードは3行目のd
が未定義のためエラーになります。
block:
var d = "d"
echo d # Error!
block
内ではbreak
が使えます。
blockにlabelをつけることもでき(break mylabel:
)、break mylabel
などとすることで特定のblockから抜けることもできます。
while, forなどと組み合わせることで複雑な制御フローも記述できそうですね(あまり複雑にはしないほうがいいと思いますが)。
block large:
for i in 1..10:
for j in 1..10:
echo(i, j)
if j==3:
break # 内側のforルールからしか抜けない
# 11, 12, 13, 21, 22, 23, ..., 103 と出力
block large:
for i in 1..10:
for j in 1..10:
echo(i, j)
if j==3:
break large # largeブロックを抜ける
# 11, 12, 13 だけ出力
参考: 公式チュートリアル: Scopes and the block statement
宣言は proc プロシージャ名(変数1: 型, 変数2: 型, ...): 戻り値の型 =
です。
この後に改行してインデントされたブロックに処理を記述します。
記号が少し違いますが、概ねPythonの関数アノテーション(例えばmypy)と同じ記法です。
例えばフィボナッチ数列を返すプロシージャは以下のように書けます。
proc fib(n: int): int =
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
また、短い処理であれば処理も同じ行にまとめて以下のように一行で書くこともできます。
proc isPositive(i: int): bool = i >= 0
echo isPositive(3)
上記の例ではreturn
文で値を返していますが、プロシージャ内では自動的にresult
という変数が戻り値として指定された型で宣言されています。return
されずに関数の最後に到達した場合や、return
とだけ書かれた行に到達した場合、自動的にresult
の値が返されます。
したがって、上記のフィボナッチ数列のプロシージャは以下のように書くこともできます。
proc fib(n: int): int =
if n < 2:
result = n
else:
result = fib(n - 1) + fib(n - 2)
二つの方法があります。
一つは一般的な proc_name(arg1, arg2, arg3, ...)
の形です。
上記のコードでもこの方法を使っています。
もうひとつは、いわゆるメソッド呼び出しの様に arg1.proc_name(arg2, arg3, ...)
と呼び出す形です。
この時、.
の前の値がプロシージャの第一引数となり、カッコの中の引数が二つ目以降の引数として扱われます。
プロシージャが引数を一つしか取らない場合、括弧は省略して arg1.proc_name
と書くこともできます。
二つ目の書き方は始め違和感を覚えたのですが、プロシージャの第一引数は型が決まっていて他の型には使えないので、プロシージャを定義することは第一引数の型にメソッドを追加するのと同じだと考えると納得出来ました。静的型付言語だと当たり前なのかもしれませんが、動的型付言語ばかりやっていたので非常に新鮮でした。
プロシージャの引数として値だけ与えられた時は引数が定義された順に扱われます。
また、引数名=値
とすることで順序を無視することができます。
proc proc_name(a: int, b: string): string =
result = ""
for i in 1..a:
result = result & b
echo(proc_name(10, "b")) # >>> bbbbbbbbbb
echo(proc_name(b="b", a=10)) # >>> bbbbbbbbbb
以下の様に引数のデフォルト値を与えることもできます。
proc proc_name2(a: int, b: string = "a"): string =
result = ""
for i in 1..a:
result = result & b
echo(proc_name2(10)) # >>> aaaaaaaaaa
前述したように、プロシージャは return
文がなくても必ず result
の値を返します。
したがって、以下の様に戻り値が不要な関数を呼び出す時には、戻り値を"捨てる"ことを明示しなくてはなりません。
そのために discard
を使用します。
proc proc_name3(a: int, b: string = "a"): string =
var c = ""
for i in 1..a:
c = c & b
echo c
discard proc_name3(10, "c") # >>> cccccccccc
proc_name3(10, "c") # ERROR!
また、この discard
文と複数行文字列("""文字列"""
)を利用することで、複数行のコメントにできます。
discard """これは
複数行の
コメント"""
プロシージャは使われる前に宣言されなければなりません。 したがって、以下のコードはエラーになります。
echo(proc_name5) # Error! Undefined identifier: 'proc_name5'
proc proc_name5(a: int, b: string = "a"): string =
result = ""
for i in 1..a:
result = result & b
先に宣言を書くことで、このエラーを回避できます。実装を伴わない宣言では、最後の=
を書きません。
proc proc_name5(a: int, b: string = "a"): string
echo(proc_name5(10)) # OK!
proc proc_name5(a: int, b: string = "a"): string =
result = ""
for i in 1..a:
result = result & b
上記の例では有用性がありませんが、公式のチュートリアルにより現実的な例があります。
無名関数も使えます。 nim-by-exampleの例をそのまま借ります。
import sequtils
let powersOfTwo = @[1, 2, 4, 8, 16, 32, 64, 128, 256]
echo powersOfTwo.filter(proc (x: int): bool = x > 32) # 無名関数1
echo powersOfTwo.filter do (x: int) -> bool : x > 32 # 無名関数2
@[...]
はSeqs型 (sequence) のエイリアスです。 Seqs型は可変長の配列(リスト)で、要素の型が全て同一のものです。
無名関数の書き方はコード例の5, 6行目の二通りあります。(5行目のように)proc
で書く場合は、プロシージャを一行で書く記法とほぼ同じで、プロシージャ名を省いたものです。もうひとつはdo
を使った書き方で、こちらの方が多少短く書けます(が、個人的にはわかりにくい気がします)。
Nim by Exampleには、普通に定義されたプロシージャを上の様に引数にすることはできない、と書いてありますが、私が試した所、普通に定義したプロシージャ({.procvar.}
pragmaなし)でも名前を引数に使えました。
プロシージャの定義時、戻り値と=
の間に {.文字列.}
で囲まれたpragma
を書くことができます。これによって、コンパイル時にコンパイラに様々な指示を与えることができるそうです。詳細はマニュアルを参照して下さい。
discardable
pragmaを使用すると、discard
しなくてもエラーではなくなります。
proc proc_name4(a: int, b: string = "a"): string {.discardable.} =
var c = ""
for i in 1..a:
c = c & b
echo c
discard proc_name4(10, "c") # >>> cccccccccc
proc_name4(10, "c") # 今度はOK!!
noSideEffect
pragmaを使用すると、プロシージャが副作用を伴う時エラーになるらしいのですが、手元では適当な例で確認できませんでした。
以下のコードはnim-by-exampleでSide effectの例として書かれていたものですが、普通に実行できました。
proc sum(x, y: int): int {. noSideEffect .} =
x + y
proc minus(x, y: int): int {. noSideEffect .} =
echo x # error: 'minus' can have side effects
x - y
参考: http://nim-lang.org/manual.html#nosideeffect-pragma
他にもpragmaはたくさんあります。
参考: http://nim-lang.org/manual.html#pragmas
プロシージャに似たものにイテレータがあります。 Pythonのイテレータのようなものだと思います。
組み込みの countup
相当のイテレータの定義は以下のようになります。
iterator myCountup(a, b: int): int =
var res = a
while res <= b:
yield res
inc(res)
for i in myCountup(1, 7):
echo i
プロシージャとの違いは以下のとおりです。
- 宣言に
proc
ではなくiterator
を使う return
ではなくyield
を使うfor
ループからの呼び出しが可能 (※要確認、nextのような関数はない?)result
の暗黙的な宣言は行われない- その他、詳細はチュートリアルなどを参照して下さい
基本的な型には、真偽値、Character型、文字列型、整数型、浮動小数点型などがあります。他にArrayやSet, Enumなどがあります。
true
と false
の二値のみです。
1バイトの文字です。シングルクォート('
)で囲みます。
var a: char = 'a'
echo a # >>> a
var b: char = 'ss' # エラー
var b: char = 'あ' # エラー
いわゆる文字列です。ダブルクォート("
)で囲んで定義します。
var s: string = "String"
echo s # >>> String
echo(s.len()) # >>> 6 長さも求められます
for i in s: # シーケンシャルアクセスも可能です
echo i # 一行ずつS, t, r, i, n, gと出力
初期化していない場合は nil
になります。
空文字列 ""
ではありません。
var s: string
if s == nil:
echo "nil"
else:
echo "string"
# >>> nil
&
演算子で文字列同士を連結できます。PythonやJavaScriptなどとは違い、デフォルトでは+
演算子で文字列を連結することはできません。
let s1 = "aaa"
let s2 = "bbb"
echo s1 & s2 # >>> aaabbb
# echo s1 + s2 # Error!
マルチバイト文字も扱えますが、長さなどは正確に求められません。そんな時はunicodeモジュールを使うとなんとかなるかもしれません。
var a: string = "文字列"
echo a # >>> 文字列
echo(a.len()) # 9 ← bytee数で数えられてしまう
from unicode import runeLen # unicodeモジュールには他にも便利な関数があるそうです
echo(a.runeLen()) # 3
unicodeといいますか、多言語のサポートはまだ十分ではないように感じます。 特にunicode以外のマルチバイト文字の扱いがよくわかりませんでした。 Windowsで日本語パス名を扱う時や、コマンドプロンプトで日本語入力を受ける時にどうなるのかは未確認です。
以下の10種類です。
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64
特に指定がなければint
が使用されます。以下のようにシングルクォート('
)を使ってsuffixで型を指定することもできます。
let
a = 1 # int
x = 5'i8 # int8
y = 143'i64 # int64
u = 0'u # uint
以下の3種類です。
float float32 float64
特に指定がなければfloat
が使用されます。整数型と同様、suffixで型を指定することもできます。
let x = 0.0'f32 # float32
Pythonでも3.4で導入されたEnum型ですが、私はよく理解できていません。
整数型またはEnum型の一部範囲から構成される型(subrange)を定義できます。 以下の例ではSubInt型を 0 ~ 5 の6つの整数からなる型として定義しています。 範囲外の値にアクセスするとエラーになります。
type
SubInt = range[0..5]
var sr: SubInt = 0
echo sr
for s in 0..10:
sr = sr + 1
echo sr # 0, ..., 5まで出力され、以下のエラーが出る(実行時エラー)
# Error: unhandled exception: value out of range: 6 [RangeError]
単一の型のみで構成される固定長配列です。[]
で作れます。
let arr = [1, 2, 3, 4, 5]
for s in arr:
echo s # 1, ..., 5 を縦に出力
type
宣内言でarray[インデックス範囲, 型]
を使うことで、独自の型を定義することも可能です。
type
# 0 ~ 5のインデックスでアクセスできる、長さ6で要素の型がintの配列型を定義
IntArray = array[0..5, int]
IntArray1 = array[6, int] # 要素数だけを指定する事も可能(0始まり)
var
x: IntArray
x = [1, 2, 3, 4, 5, 6]
for i in low(x)..high(x):
echo(x[i])
var y: IntArray
y = [1, 2, 3, 4, 5] # 要素数が足りないのでエラー
y = [1, 2, 3, 4, 5, 6, 7] # 要素数が多すぎるのでエラー
インデックスは0始まりでなくてもOKです。
type
# 10 ~ 15のインデックスでアクセスできる配列
IntArray2 = array[10..15, int]
var z: IntArray2
z = [1, 2, 3, 4, 5, 6]
echo z[10] # >>> 1
echo z[1] # エラー
for
ループで2つの値を与えると、インデックスとそれに対応する値を返します。
# 上記のコードの続き
for i, v in z:
echo("i=", $i, ", v=", $v)
# >>> i=10, v=1
# >>> i=11, v=2
# >>> i=12, v=3
# >>> i=13, v=4
# >>> i=14, v=5
# >>> i=15, v=6
インデックスとそれに対応する値を同時に取れるのは便利ですね。
Pythonだとわざわざenumerate
関数を使う必要がありました。
# pythonの場合
for i, v in enumerate(range(10, 20)):
print([i, v]) # >>> [0, 10] ... [9, 19]
Sequence型は可変長のArrayです。
Arrayに@
演算子を作用させることで作れます。
インデックスは常に0始まりで、i
番の要素にアクセスする方法はArray同様x[i]
です。
var x: seq[int] # a sequence of integers
echo(repr(x)) # >>> nil 初期値はnil
x = @[1, 2, 3, 4, 5, 6] # @でArrayをSeqに変換
echo(repr(x)) # >>> 0x7f3f1fd5b050[1, 2, 3, 4, 5, 6] なぜかメモリ番地的な出力
echo($x) # >>> @[1, 2, 3, 4, 5, 6] これは期待通り
Array同様、for
ループで使えますし、indexと値を同時に取ることも可能です。
for i in @[3, 4, 5]:
echo($i) # >>> 3, 4, 5と順に出力
for i, v in @[3, 4, 5]: # Arrayの様にindexと値を一度に値に入れることが可能
echo("i=", $i, ", v=", $v)
# >>> i=0, v=3
# >>> i=1, v=4
# >>> i=2, v=5
数学で言う所のセットだそうです。{}
で定義できます。
要素にはint, char, boolなどordinal型のみ利用可能です。
type
TCharSet = set[char]
var
s: TCharSet
s = {'a'..'f'}
let
t: TCharSet = {'d'..'k'}
echo($s) # >>> {a, b, c, d, e, f}
echo($t) # >>> {d, e, f, g, h, i, j, k}
let u = s + t # Union of two sets
echo($u) # >>> {a, b, c, d, e, f, g, h, i, j, k}
let d1 = s - t # Difference of two sets
let d2 = t - s # Difference of two sets
echo($d1, ", ", $d2) # >>> {a, b, c}, {g, h, i, j, k}
let i = s * t # Intersection of two sets
echo($i) # >>> {d, e, f}
フラグ管理などに便利との事です。
引数の数が不定なプロシージャで使われます。 引数は自動的にArrayに変換されます。 この引数は最後でなくてはなりません。
proc print(b: varargs[string]) =
var res = ""
for s in items(b):
res = res & s
echo res
print("a", "b", "c", "d") # >>> abcd
上記のコードはitems()
がなくても動きます。
itemsの説明はよく理解できませんでした。
配列の一部などを切り出すスライスです。
var
a = [1, 2, 3, 4, 5]
b = "abcdefghijk"
echo a[2..3] # >>> @[3, 4] Arrayの一部を取得
echo b[2..3] # >>> cd 文字列にも使える
b[2..3] = "CDDDD" # 置き換える事もできる
echo b # >>> abCDDDDefghijk
Pythonのタプルとは違います。 複数の名前付きフィールドを持つ型を定義するものです。 感覚としては、Pythonのクラスの低機能版が近いような気がします。
type
TPerson = tuple[name: string, age: int]
var person: TPerson
person = (name: "Peter", age: 30)
# person = ("Peter", 30) でも同じ
# .field でアクセス可能
echo(person.name) # >>> Peter
echo(person.age) # >>> 30
# indexでもアクセス可能
echo(person[0]) # >>> Peter
echo(person[1]) # >>> 30
# type宣言なしでもOK
var personB : tuple[name: string, age: int] = ("Mark", 42)
echo($person) # >>> (name: Peter, age: 30)
echo($personB) # >>> (name: Mark, age: 42)
person = personB # OK! 同じフィールド名・タイプが同順なので同じ型扱い
echo($person) # >>> (name: Mark, age: 42)
Tupleに似ていますが、Objectの方が多機能で、継承や隠蔽ができるそうです。 オブジェクト指向な書き方をする時、Objectがクラスに相当するのでしょうか。
- 追跡される参照: reference (
ref
で宣言) - 追跡されない参照: pointer (
ptr
で宣言)
Referenceの方が安全。 Pointerは低レベル処理に必要になるかも。
Procedureへの参照(Pointer)。 ProcedureをPythonでの第一級関数の様に扱えるので、関数型言語のような書き方ができます。
無名関数の節の例を再利用します。
import sequtils
let powersOfTwo = @[1, 2, 4, 8, 16, 32, 64, 128, 256]
echo powersOfTwo.filter(proc (x: int): bool = x > 32) # 無名関数1
echo powersOfTwo.filter do (x: int) -> bool : x > 32 # 無名関数2
proc gt32(x: int): bool =
if x > 32:
result = true
else:
result = false
echo powersOfTwo.filter(gt32) # 引数にProcedureを与えられる
# >>> @[64, 128, 256]
$
演算子をつけることで文字列に変換できます。
let i = 1
# echo "String" & i # Error
echo "String" & $i # OK, >>> String1
repr()
でも文字列に変換できます。こちらのほうが変換できる型が多いです。
let arr = [1, 2, 3, 4, 5]
# echo arr # Error
# echo $arr # Error
echo repr(arr) # OK, >>> [1, 2, 3, 4, 5]
toInt
, toFloat
を使います。toInt
では値は四捨五入されます。
let i = 1
let f = 2.3
let f2 = 2.7
echo f.toInt # >>> 2
echo f2.toInt # >>> 3
echo i.toFloat # >>> 1.0
strutils
モジュールを使います。
from strutils import parseInt, parseFloat
let
si = "13"
sf = "43.5"
# echo si + 3 # Error
# echo sf + 3.4 # Error
echo si.parseInt + 3 # >>> 16
echo sf.parseFloat + 3.4 # >>> 46.8
例外はオブジェクトで、わかりやすくするために例外型にはsuffixとしてError
がつけられます。
例外は raise
文で発生させられます。
var
e: ref OSError # 既存の例外を参照
new(e) # 新規にオブジェクトを作っている?のかな?
e.msg = "the request to the OS failed" # msgプロパティに値は例外発生時に表示される
raise e # 例外発生
# >>> Traceback (most recent call last)
# >>> file_name.nim(5) exception
# >>> Error: unhandled exception: the request to the OS failed [OSError]
# >>> Error: execution of an external program failed
上記のコードは、system
モジュールの newException
テンプレートを使うことで一行で書けます。
raise newException(OSError, "the request to the OS failed")
構文はだいたいPythonと同じです。
var f: File
if open(f, "sometext.txt"):
try:
... # 何か処理
except SomeError: # try節でSomeErrorが発生したらここ
echo "some error"
except AnotherError: # AnotherErrorが発生したらここ
echo "another error"
except: # その他のエラーが発生した場合はここ
echo "unknown erro"
raise # raiseだけの場合、同じ例外を上に投げる
finally:
echo "finally" # 必ず実行される
try-finally
のようなものです。
defer文より後の同じブロック内の処理がtry節に入っているのと同じです。
defer: do-something
のdo-something
部分がfinaly
で実行される処理と同じです。
- try-finally で書いた場合
var f = open("numbers.txt")
try:
f.write "abc"
f.write "def"
finally:
close(f)
- defer で書いた場合
var f = open("numbers.txt")
defer: close(f)
f.write "abc"
f.write "def"
モジュール(別ファイル)に分割することができます。
モジュールに分割することで、名前空間を分割してプログラムを書くことができます。
他のモジュールのシンボルへはimport
文によってアクセスします。
外部モジュールから参照できるシンボルは、トップレベルかつ *
付きのシンボルだけです。
# Module A
var x*, y: int
proc `*` *(a, b: seq[int]): seq[int] =
newSeq(result, len(a))
for i in 0..len(a)-1:
result[i] = a[i] * b[i]
when isMainModule:
assert(@[1, 2, 3] * @[1, 2, 3] == @[1, 4, 9])
上記のコードでは、x
と *
は他のモジュールでimport可能ですが、 y
はimportできません。
トップレベルの宣言はプログラムの開始時に実行されます。これは、例えば複雑なデータ構造を初期化するために使うことができます。
各モジュールには特別な定数、isMainModule
が定義されています。
この定数は、そのモジュールがメインファイルとしてコンパイルされた時に true
になります。
これを使って、先ほどの例の様にモジュール内にテストを埋め込むことができます。
モジュールをコンパイルする流れは、以下のようになっています。
- いつものように、モジュール全体をコンパイルし、
import
文にしたがって繰り返していく - パース済みのシンボルだけがexportされる。もし未知のシンボルがあれば中止する
以下の例がわかりやすいです。
# Module A
type
T1* = int # Module A exports the type ``T1``
import B # the compiler starts parsing B
proc main() =
var i = p(3) # works because B has been parsed completely here
main()
# Module B
import A # A is not parsed here! Only the already known symbols
# of A are imported.
proc p*(x: A.T1): A.T1 =
# this works because the compiler has already
# added T1 to A's interface symbol table
result = x + 1
外部モジュールのシンボルは、module.symbol
の構文ですることができます。
シンボルが(どのモジュールのものを指しているのか)曖昧な場合は、上記の構文で限定されなくてはなりません。
複数のモジュールで定義され、両方のモジュールが第三のモジュールにインポートされた時、そのシンボルは曖昧です。
# Module A
var x*: string
# Module B
var x*: int
# Module C
import A, B
write(stdout, x) # error: x is ambiguous (AかBか区別できない)
write(stdout, A.x) # no error: qualifier used (Aのxと一意に決まる)
var x = 4 # ここで自モジュールのxを作成
write(stdout, x) # not ambiguous: uses the module C's x
ただし、この規則はprocedureとiteratorには適用されない。 以下の例ではオーバーロードの規則が適用される。
# Module A
proc x*(a: int): string = $a
# Module B
proc x*(a: string): string = $a
# Module C
import A, B
write(stdout, x(3)) # no error: A.x is called
write(stdout, x("")) # no error: B.x is called
proc x*(a: int): string = nil
write(stdout, x(3)) # ambiguous: which `x` is to call?
通常の import
文はエクスポートされている全てのシンボルを取得してしまう。
これは除外するシンボルを except
で指定することで制限できる。
import mymodule except y
単純なimport
文は全てのシンボルをインポートする。インポートしたいシンボルだけを指定したい場合は、 from ... import ...
を使う。
from mymodule import x, y, z
from
文では、名前空間制限を強制している。したがって、モジュール名.シンボルの形式で全てのシンボルにアクセスできる。
from mymodule import x, y, z
x() # use x without any qualification
from mymodule import nil
mymodule.x() # must qualify x with the module name as prefix
x() # using x here without qualification is a compile error
モジュールに別名を付けて短くする事もできる。
from mymodule as m import nil
m.x() # m is aliasing mymodule
通りすがりです。とても見やすくまとめていただきありがとうございます。
case文 は各々の都度判定を避けることができるので、分岐の数がとても多い場合には処理速度とメモリ効率両方の面で有用なんだと思います。