はじめに
Nim言語を学ぶ中で自分用の備忘録を残す。
コードはほぼ参考文献からのコピペ。
環境
Nim Compiler Version 2.0.2 [Linux: amd64]
nimble v0.14.2
Nim Tutorialを読むにあたって
Nim TutorialをChromeで開いてそのままページを一括でGoogle翻訳すると、ソースコード部分まで翻訳されてしまって非常に読みづらかった。
下記記事で紹介されている手法を試したところ、見事にソースコードが翻訳されなくなりました。
ありがとうございます。Nimの学習を諦めかけるところでした…
GitHubのREADMEでGoogle翻訳をかけるとプログラムコードも日本語化されて煩わしい思いをしている君へ
コンパイル/実行方法
nim compile --run test.nim
オプション名/拡張子は短縮可能。
nim c -r test
コメント
#で始まる。
# var a: int
#[ ]#でネスト可能。
#[
var b: int
var c: int
#[
var d: int
var e: int
]#
]#
変数
初期化
複数の変数を,で区切って宣言し、初期値を与えた場合、C言語と異なり全ての変数がその初期値で初期化される。
var x, y: int = 1
echo x # 1
echo y # 1
varとletとconst
- var:再代入可能な変数。
- let:再代入不可な変数。
- const:再代入不可な定数。コンパイル時に値が決定される必要がある。
letとconstは初期化必須
const input = readLine(stdin) # Error: constant expression expected
let input = readLine(stdin) # works
条件式
case文
case文では、caseの対象となる変数が取りうる全ての値を処理できるようにofとelseを記載しなければならない。
# コンパイルエラー
const x = 0
case x:
of 0: echo 0
of 1: echo 1
# xが0もしくは1以外の場合の処理が必要
# これならOK
const x = 0
case x:
of 0: echo 0
of 1: echo 1
else: echo 2
Block文
breakを用いてBlockを途中で終了することが出来る。
block myblock:
echo "entering block"
while true:
echo "looping"
break # leaves the loop, but not the block
echo "still in block"
echo "outside the block"
block myblock2:
echo "entering block"
while true:
echo "looping"
break myblock2 # leaves the block (and the loop)
echo "still in block" # it won't be printed
echo "outside the block"
when文
C言語の#ifdefとほぼ同じ。
when system.hostOS == "windows":
echo "running on Windows!"
elif system.hostOS == "linux":
echo "running on Linux!"
elif system.hostOS == "macosx":
echo "running on Mac OS X!"
else:
echo "unknown operating system"
関数
result変数
値を返すプロシージャには返り値の型のresult変数が暗黙的に宣言されている。
また、プロシージャの終了時にreturn文が無い場合、resultの値が自動でreturnされる。
返り値の型を書かない場合は、返り値の型は暗黙的にvoidとなる。そのためvoidの記載を省いても良い。(書いても良い)
# 全部同じ
proc f1(x: int): int =
return x
proc f2(x: int): int =
result = x
proc f3(x: int): int =
result = x
return result
proc f4(x: int): int =
result = x
return
return文が無く、result変数を使用しないプロシージャの場合は、プロシージャ内の最後の式の値を返す。
proc helloWorld(): string =
"Hello, World!"
注意
返り値が参照型の場合、result変数はnilで初期化されているため、手動で初期化が必要。
仮引数の宣言
仮引数で異なる型を持つ複数個の変数を宣言する時、「,」が混ざって読みにくくなる。
可読性を上げるために「;」で区切ることが出来る。
proc f1(a, b, c:int, d, e: string): int =
return 1
proc f2(a, b, c:int; d, e: string): int =
return 2
引数の値を変更する
デフォルトではプロシージャ内で引数の値は変更できない。
仮引数の宣言時にvarをつけるとプロシージャ内で実引数の値を変更できるようになる。
ただし、その際は実引数で渡す変数もvarで定義されている必要がある。
proc test(arg_v: int, arg_ref: var int) =
#arg_v = 1 # コンパイルエラー
arg_ref = 1
var x, y: int
test(arg_v = x, arg_ref = y)
echo x # 0
echo y # 1
戻り値の破棄
Nimでは関数の戻り値を暗黙的に破棄することは許可されてない。
proc test(): int =
return 1
#test() #コンパイルエラー
呼び出した関数の戻り値を破棄したい場合はdiscard文を使用する必要がある。
discard test()
また、{.discardable.}プラグマを使用して関数が定義されている場合は、その関数の戻り値は無視できる。
proc test(): int {.discardable.} =
return 1
test() #OK
型
文字列型
文字列変数の初期値を与えない場合、空文字列""で初期化される。
NimはUnicodeを採用し、基本的なエンコーディング方式としてUTF-8を採用することでマルチバイト文字を扱うことが出来る。
echo "Hi" # => "Hi"
echo "文字列" # => "文字列"
# スライス操作も可能, ただしバイト列を扱っていることに注意
# echo "文字列"[0 .. ^2] # こちらは表示崩れを引きおこす
echo "文字列"[0 .. ^4] # => "文字"
let str = "日本語"
echo len(str) # => 9: バイトの長さが返ってくる.
# 文字列の長さが欲しい場合, runeLenを使うことができる.
import unicode
echo runeLen(str) # => 3
# 普通にforを適用した場合, バイト列に対する反復操作になる.
for c in str:
echo $c.int
# 各文字に対する反復操作はunicodeモジュールのutf8を使う.
for c in str.utf8:
echo c
演算子
左ビットシフト演算子:shl
右ビットシフト演算子:shr
は常に引数をunsignedとして扱う。
算術シフトしたい場合は通常の乗算もしくは除算を使用する。
unsignedの演算は全てラップアラウンドする。
配列(array)
固定長配列。配列の各要素は同じ型を持つ。
配列コンストラクタ[]を使用して初期化出来る。
代入演算子は、配列の内容全体をコピーする。
# どっちも同じ
type
IntArray1 = array[0..5, int]
IntArray2 = array[6, int]
- len(a): 配列aの長さを返す
- low(a): 配列aの最小のインデックスを返す
- high(a): 配列aの最大のインデックスを返す
シーケンス(seq)
可変長配列。常にヒープ上に割り当てられ、GCの対象になる。
var
x: seq[int] # a reference to a sequence of integers
x = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heap
シーケンスコンストラクタ@[]により初期化出来る。
newSeq関数を使ってもシーケンス型のオブジェクトを作成できる。
また、文字列でも配列でもシーケンス型でも、スライスで返される値はシーケンス型となる。
for文をシーケンスと共に使用する場合、1つまたは2つの変数と共に使用できる。
1つの変数の場合、for文はsystemモジュールのitems()イテレータからの結果をループし、変数はシーケンスの要素値を保持する。
2つの変数の場合、for文はsystemモジュールのpairsイテレータからの結果をループし、最初の変数はシーケンスのインデックス位置、2番目の変数は要素値を保持する。
for value in @[3, 4, 5]:
echo value
# --> 3
# --> 4
# --> 5
for i, value in @[3, 4, 5]:
echo "index: ", $i, ", value:", $value
# --> index: 0, value:3
# --> index: 1, value:4
# --> index: 2, value:5
オープンアレイ(openArray)
openArrayはパラメータにのみ使用できる。
プロシージャは固定長だけでなく、様々な配列を処理できる必要がある。
互換性のある要素の方を持つ任意の配列をopenArrayパラメータに渡すことが出来る。
var
fruits: seq[string] # reference to a sequence of strings that is initialized with '@[]'
capitals: array[3, string] # array of strings with a fixed size
capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements
fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime
fruits.add("Mango")
proc openArraySize(oa: openArray[string]): int =
oa.len
assert openArraySize(fruits) == 2 # procedure accepts a sequence as parameter
assert openArraySize(capitals) == 3 # but also an array type
可変長引数
varargsパラメータは引数のリストを自動で暗黙的に配列に変換する。
ただしvarargsパラメータはプロシージャの最後の引数の型でのみ使用可能。
varargsパラメータに実引数を渡す前に型変換することも出来る。
proc myWriteln(f: File, a: varargs[string]) =
for s in items(a):
write(f, s)
write(f, "\n")
myWriteln(stdout, "abc", "def", "xyz")
# これはこの形に変換されます
# myWriteln(stdout, ["abc", "def", "xyz"])
$はパラメータaに渡される引数に適用される。
文字列に適用される$はnop である。
オブジェクト型
of演算子を使ってオブジェクトの型を判断できる。
type
Human = object of RootObj
name: string
age: int
Student = object of Human
no: int
var h1: Human = Human(name: "suzuki", age: 24)
if h1 of Human:
echo "h1 = Human"
if h1 of Student:
echo "h1 = Student"
# h1 = Human
var s1: Student = Student(name: "suzu", age: 14)
if s1 of Human:
echo "s1 = Human"
if s1 of Student:
echo "s1 = Student"
# s1 = Human
# s1 = Student
var h2: Human = Student(name: "suzu", age: 14)
if h2 of Human:
echo "h2 = Human"
if h2 of Student:
echo "h2 = Student"
# h2 = Human
# h2 = Student
タプル(tuple)
オブジェクトと似ているが、同じ型および同じ名前のフィールドを同じ順序で指定する場合、異なるタプル型は同等となる。
タプルコンストラクタ()でタプルを作成することが出来る。
type
Tuple1 = tuple
i: int
c: char
Tuple2 = tuple[i: int, c: char]
Tuple3 = (int, char)
# Tuple4 = tuple[int, char] # コンパイルエラー
var t1: Tuple1 = (i: 10, c:'a')
var t2: Tuple2 = (i: 10, c:'a')
var t3: Tuple3 = (i: 10, c:'a')
echo t1.i, " ", t1.c
echo t2.i, " ", t2.c
# echo t3.i, "", t3.c # コンパイルエラー
echo t3[0], " ", t3[1] # OK
type
Tuple4 = tuple[ii: int, cc: char]
Tuple5 = tuple[c: char, i: int]
# var t4: Tuple4 = t1 # コンパイルエラー
# var t5: Tuple5 = t1 # コンパイルエラー
タプルのアンパック
タプルのフィールドを個別の変数に代入するには、変数を()で囲む。
import std/os
let
path = "usr/local/nimc.html"
(dir, name, ext) = splitFile(path)
baddir, badname, badext = splitFile(path)
echo dir # outputs "usr/local"
echo name # outputs "nimc"
echo ext # outputs ".html"
# All the following output the same line:
# "(dir: usr/local, name: nimc, ext: .html)"
echo baddir
echo badname
echo badext
タプルのアンパックは for ループでもサポートされる。
let a = [(10, 'a'), (20, 'b'), (30, 'c')]
for (x, c) in a:
echo x
# This will output: 10; 20; 30
# Accessing the index is also possible:
for i, (x, c) in a:
echo i, c
# This will output: 0a; 1b; 2c
参照型とポインタ型
Nimには2つの参照が存在する。
- 参照型:GCの対象となるヒープ内のオブジェクトを指す。refキーワードを使用して宣言できる。
- ポインタ型:手動で割り当てたオブジェクトまたはメモリ内の他のオブジェクトを指す。GCの対象にならない。ptrキーワードを使用して宣言できる。
空の[]表記は参照が指している項目を取得する。
new(型名)により、その型のオブジェクトをヒープに確保し、オブジェクトへの参照を得られる。
var refstring: ref string = new(string)
echo repr(refstring) # ref ""
refstring[] = "abc"
echo repr(refstring) # ref "abc"
手続き型
関数ポインタ的な。
proc f1(x: int): int =
return x + x
proc f2(x: int): int =
return x * x
type
FuncType = proc (x: int): int
proc test(f: FuncType, x: int): int =
return f(x)
echo test(f1, 3) #6
echo test(f2, 3) #9
参考文献
GitHubのREADMEでGoogle翻訳をかけるとプログラムコードも日本語化されて煩わしい思いをしている君へ