読者です 読者をやめる 読者になる 読者になる

Nim帳

プログラミング言語Nimについて書いていきます。

20160921155215

型の定義

Nimの型定義についてです。

Nimには様々な型の定義方法があります。
型定義の種類として、

  • enum
  • object
  • ref object
  • ptr object
  • 別名
  • distinct

があります。

他にも型の特性を変えるものとして、not nilがあります。

定義方法

型定義の構文は、

type 型名 = 型定義の種類

です。
型定義の種類に上記のenumなどを記述します。

enum

enumは列挙型です。
NimのenumC言語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の型は様々な定義方法があります。ゆるい型付けとしても扱えますし、堅めの型としても扱えます。それを柔軟と言うか一貫性が無いと言うかは人によると思いますが、面白い型の扱い方だと思います。