さっき決めたブログ

[Nim]Nimの文法を勉強 04 (データ型(前編))

2020年10月25日 6:10 PM01.85KNimPROGRAMNimNim文法配列構造体

Nimの構文についての勉強の4回目になります。

今回は、標準で用意されているNimの様々なデータ型について調べていきます。
少し長いので、前編・後編に分けようと思います。
今回は前編です。
また、後編では、オブジェクト指向について、NimにはClassの構文はありませんが、これから紹介するデータ型を駆使して、オブジェクト指向のプログラミングを書くことはできますので、最後にその辺りもやっていこうと思います。

typeキーワード

Nimでは type キーワードを用いることで、独自の型を作成することが出来ます。

実行結果
50

Distinct Type

type文は通常、ベースとなった型からの代入は可能ですが、distinctキーワードを用いる事で、ベースとなった型と違う型であることを明示的に示す事ができます。

列挙型(enum)

Nimでの列挙型の定義は以下の通りとなります。

実行結果
TAKO

列挙型の各要素は改行もしくはカンマで区切って定義します。
また、外の変数との重複を避けるため、完全修飾([型名].[要素名])での表記もできます。
次の例は、上のコードと同じです。

各要素は内部的には int型の整数で、デフォルトでは 0 から順番に採番されています。
値を明示的に指定することも可能ですが、その場合は他の要素と値が被らないこと、及び先頭から昇順に並んでいる必要があります。

先にも述べたとおり各要素は内部的には整数なので、整数同様比較演算が使えます。
また ordプロシージャを用いることで、整数値に変換することが出来、
先頭に $ を付与することで、要素名を文字列に変換することが出来ます。

実行結果
すごいタコ!!
10
UNI

あと、値には数字ではなく文字列も指定できるようです。

実行結果
烏賊
0

序列型(Ordinal type)の計算について

序列型という型があるわけではなく、列挙型(enum), 整数型(interger), char型, boolean型を総じて序列型と呼んでいます。
(この後説明するサブレンジについても序列型になります)
序列型は以下の操作が出来ます。

序列型の操作
ord(x)xを表す数値を返します。 intで使用してもあまり意味がないので、enum や char, boolean で使用するものだと思います。
実行結果
true
1
inc(x)x を 1増加させます。
inc(x, n)x を n増加させます。
dec(x)x を 1減少させます。
dec(x, n)x を n減少させます。
succ(x)x を 1増加した値を返します。
succ(x, n)x を n増加した値を返します。
pred(x)x を 1減少した値を返します。
pred(x, n)x を n減少した値を返します。

inc と succ(dec と pred)の違いですが、
inc, dec は変数 x の内容を直接変更します。
対して succ, pred は x の内容は変更せずに結果を戻り値として返します。

ちなみに、enum で値指定して定義した際、連続した数値でないと、ord 以外の命令はコンパイルエラーになるようです。

サブレンジ

range を用いることで数値の範囲を制限できます。
範囲外の値を設定しようとした場合、コンパイルエラーもしくはランタイムエラーになります。

実行結果
D:\Projects\nim\nimtest01\test01.nim(6) test01
D:\devtools\nim\lib\system\fatal.nim(49) sysFatal
Error: unhandled exception: value out of range: 11 notin 0 .. 10 [RangeError]

普通に range[0..10] とか書くと、intのサブレンジが出来るのですが、他の型で作りたい場合はどうするのが良いのだろう?
一応、次のような感じで出来たが、もう少しスマートな書き方はないものか?

実行結果
1

集合型(set)

集合型は、集合を扱うための型になります。
例えば次の例では、char型の値の範囲を持つ集合を定義し、'a'~'z' のアルファベットと、'0'~'9' の数値を要素として持つ集合を作成しています。

実行結果
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}

集合型で使用できる演算は以下の通りです。

集合型で使用できる演算
A + B集合の和
A * B集合の積
A - B集合の差
A == B2つの集合が等しい
A <= BA は B に含まれる、もしくは等しい
A < BA は B に含まれるが等しくない
e in A要素 e は A に含まれる
e notin A要素 e は A に含まれない
contains(A, e)要素 e は A に含まれる(e in A と同じ?)
card(A)A の要素数
incl(A, elem)A = A + {elem} と同じ
excl(A, elem)A = A - {elem} と同じ
実行結果
A + B:          {0, 1, 2, 3, 4}
A * B:          {2}
A - B:          {0, 1}
A == B:         false
C <= A:         true
C < A:          false
1 in A:         true
1 notin A:      false
contains(A, 1): true
incl(A, 4):     {0, 1, 2, 4}
excl(A, 1):     {0, 2, 4}

set型は内部でビットフィールドで定義されているため集合の要素数に制限があり、現時点では 2^16個(65,536個)の要素までしか表現出来ないそうです。
それでも 65,536bit = 8,192byte なので、(最大要素で定義した場合)1つの変数に8Kb以上のメモリを要することになるので、不用意にたくさん使うとメモリ不足などの問題もおきそうです。
ちなみに、型推論を用いた場合最大要素数で定義されるようです。

実行結果
8192

小さな型を明示的に指定して定義した場合、変数のサイズが少なくなります。

実行結果
32

「必要な要素数は2,3個なんで、1byteのビットフィールド作りたいんだけど...」
という場合は、enum と一緒に用いると定義できるようです。
enum の要素数が8個以下の場合、1byteのビットフィールドが作成されます。

実行結果
1
2

配列(array)

Nimにおける配列(array)は、サイズ変更不可の固定長コンテナとなります。
配列は array[(要素数), 型] で作ることが出来ます。
下の例では 0~6 のインデックスを持つ、int型の array を作成しています。

実行結果
[1, 1, 9, 2, 2, 9, 6]
9

多次元配列は、以下の様に定義できます。

実行結果
7

配列操作用のプロシージャとしては以下のようなものがあります。

配列操作用のプロシージャ
len(x)配列 x の長さ
low(x)配列 x のインデックスの下限
higi(x)配列 x のインデックスの上限
実行結果
len(x):     4
len(x[0]):  3
low(x):     0
high(x):    3
high(x[0]): 2

配列のインデックスや型には enum を使用することも出来ます。

実行結果
[y2, y3, y1, y1]

動的配列型(seq)

動的配列型(seq)は、配列に似ていますが、インデックスの範囲を動的に変更することが出来ます。

seq は @[] で初期化します(定義しただけでは nil となっているため使えません)。

seq は arrayで使用出来るプロシージャ以外にも以下のものが使用できます。

seqで使用できるプロシージャ
newSeq[N](x)インデックス数が x で N型の動的配列を作成します。
newSeqOfCap[N](x)インデックス数は 0 だが、x 分のキャパシティを持ったN型の動的配列を作成します。
setLen(x)配列のインデックス数を x にします。
add(a)配列の末尾に要素 a を追加します。
insert(a, x)配列の x 番目に要素 a を追加します。
delete(x)x 番目の要素を削除します。 削除後も配列の並び順は保証されます。
del(x)x 番目の要素を削除します。 削除後は配列の並びは保証されません。 delete と比較してこちらの方が処理速度が早いのだと思われます。
pop()末尾の要素を取得して、削除します。
x & y動的配列 x と y を結合します。
実行結果
x.add(5):      @[10, 0, 0, 5]
x.insert(6, 1):@[10, 6, 0, 0, 5]
x.delete(2):   @[10, 6, 0, 5]
x.del(0):      @[5, 6, 0]
x.pop():       0
x &= @[7, 8]:  @[5, 6, 7, 8]

オープン配列(Open array)

オープン配列はプロシージャのパラメータとしてのみ使用することが出来ます。

配列をプロシージャのパラメータとして渡す場合、定義と呼出で、配列の型・インデックスが一致していないとコンパイルエラーになるため、非常に使い勝手が悪いです。

openArray を用いると、その辺りを柔軟に対応できます。
配列の型は合っている必要がありますが、固定長・可変長・インデックスのサイズに関わらず呼出すことができます。

実行結果
0
1
2
3
4
5

尚、openArray では多次元配列の受渡しは出来ないそうなので、ちょっと注意が必要です。

可変長引数(Varargs)

varargs パラメータは openArray と似ていますが、これは可変長引数を受け渡す際の、仕組みになります。

実行結果
red
blue
green

また、以下のように第2パラメータに `$` を追加すれば、文字列の引数に数値を渡しても、文字列に自動変換してくれるようなのですが、他の使い方がよく分かりません。

実行結果
red
blue
333

スライス

スライスは、文字列や配列の一部分を切り取ったり、指定した範囲の変更を行うための構文になります。

実行結果
kani[3..6] : Tako
kani :       IkaカニUniEbi
ebi :        [1, 4, 3]
tai :        @[1, 4, 5, 6, 3]

結構長くなったので、今回はここまでにします。 次回、オブジェクト型以降の説明は後編 に続きます。

参考にしたサイトなど

Nim Tutorial (Part I)
https://nim-lang.org/docs/tut1.html
Nim Tutorial Part Iを日本語訳してみた(後編)
https://qiita.com/KTakahiro1729/items/3f18811267bf4f8075d5
【Nim】個人的逆引きリファレンス
http://flat-leon.hatenablog.com/entry/nim_howto
DeepL翻訳
https://www.deepl.com/ja/translator

投稿者プロフィール

KARASU
うーん いろいろ考え中。。。

コメント

コメント取得中...

関連記事

TOPへ