Nimチュートリアル Part1 要点まとめ

# NimチュートリアルPart1 まとめ

[NimチュートリアルPart1](https://nim-lang.org/docs/tut1.html)の個人的要点まとめ。
**Ver0.20.0**

## Lexical Element
- タブ文字はインデントとして識別されない
- [systemモジュール](https://nim-lang.org/docs/system.html)はすべてのファイルに暗黙的にインポートされている
- `##` はドキュメントコメント
  - コメントではあるがSyntax上の制限があり、どこでも書けるわけじゃない
- `#[  ]#` で複数行コメント
- `r"hoge\fuga"` は raw文字列
  - `"""aaa"""`で複数行
- 予め `var x: int` で変数宣言しないと `x = 1` はできない。
- 複数変数への代入が可能だが　``var x, y = foo()` と記述すると `foo()` が合計で2回呼ばれるので注意


## Value Type
- 値型
  - `=` での操作は基本的にコピーが渡される
    - 参照渡しではないため巨大オブジェクトを `=` すると高コストになることがある
      ので注意


## let, const
- `const` はコンパイル時定数
  - バイナリの `.data` 領域に設置されるデータのこと
- `let` は一度代入されると、変更不可になる。
  - `let a = readLine()` はOKだが `const` だとエラー


## Numbers
- `1_000_000`
  - 可読性のため `_` の挿入が可能
- `1.0e9`
- `0x` で16進数 
- `0b` で2進数 
- `0o` で8進数

## case文
- `switch` がない
- `else: discard` で明示的に `default` な処理を入れないとコンパイルできない

```nim
 case name
 of "":
   echo "Poor soul, you lost your name?"
 of "name", "hoge", "fuga":
   echo "foo"
 else: discard
```


## discard
- `discard "hogefuga"` でデータを捨てられる
- 単文としての `discard` は Python でいう `pass`
  - 何もしない
- `Nim`は暗黙的な関数の返り値の破棄ができない
  - 返り値が不要なときは `discard someFunc()` と書く必要がある。
- `discard` なしで返り値を破棄できるようにしたい場合は `discardable` プラグマをつかう

```nim
 proc p(x, y: int): int {.discardable.} =
   return x + y
```

## for文
- `for` のなかの `i` は自動的に定義されるため事前宣言が不要

```nim
for i in 1..10:
  ...

for i in 0..<10:
  ...  # 0..9

var s = "some string"
for i in 0..<s.len:
  ...

for i in 0..^s.len:
  ... # 長さを超えるのでエラーになる
```

## block
- コードブロックを表す
  - ブロックラベルはオプション
  - ラベルがある場合は `break` でブロックから抜けることも可能になる

```nim
block myblock:
  var x:int
  ...
  break myblock # ラベルがあるとbreak先が指定可能
```

## when
- 定数式の比較しかできない
- `if` など違い新しい `block` を生成しない
- コンパイラは最初に `true` となったコードしか生成しない
  - 実質C言語の `ifdef` のようなコンパイル時スイッチのような動作

```nim
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"
```


## インデント
- Pythonと同様にコードの固まりを表す
- `(` と `and` のあとは任意のインデントが可能

```nim
# computes fac(4) at compile time:
const fac4 = (var x = 1; for i in 1..4: x *= i; x)
```


## Procedure（関数）
- `result` という変数が暗黙的に関数の最初にゼロ値で宣言されている。
  - `result` は関数の最後に暗黙的に `return` される
  - `return` のみ記述された場合も暗黙的に `return result` になる。
- 関数のパラメータはイミュータブルなので注意
  - 引数をミュータブルにしたければ同名の変数を関数内で `var` で再宣言(shadowing)
  - 最初からミュータブルなパラメータを宣言したければ `var` を引数に追加する
    - C言語の参照渡しと同じ効果

```nim
# res, remainder は呼び出し側の値も変わる
proc divmod(a, b: int; res, remainder: var int) =
  res = a div b        # integer division
  remainder = a mod b  # integer modulo operation
```


### 関数の多重定義
- C++のような関数の多重定義が可能

```nim
proc toString(x: int): string = ...
proc toString(x: bool): string =
  if x: result = "true"
  else: result = "false"
```


## 演算子
- 使える文字は基本的に ` + - * \ / < > = @ $ ~ & % ! ? ^ . |`
- `Nim` には中置演算子と前置演算子のみ
  - 後置演算子は不可

- 演算子の定義は使いたい記号を引用符で囲んで関数定義する

```nim
proc `$` (x: myDataType): string = ...
# myDataType型で $ 演算子が使用可能になる
```

- 演算子は普通の関数のように呼び出し可能

```nim
if `==`( `+`(3, 4), 7): echo "True"
```


## 前方宣言（Forward Declaration）
- C言語のプロトタイプ宣言のようなもの

```nim
# 前方宣言
proc even(n: int): bool

# 相互再起
proc odd(n: int): bool =
  assert(n >= 0)
  if n == 0: false
  else:
    n == 1 or even(n-1) # 前方宣言されているのでOK
 
proc even(n: int): bool =
  assert(n >= 0)
  if n == 1: false
  else:
    n == 0 or odd(n-1)
```

## イテレータ
- `iterator` と `yeild` キーワードを使う
  - `proc` と同じくイテレータは多重定義可能

```nim
iterator countup(a, b: int): int =
  var res = a
  while res <= b:
    yield res
    inc(res)
```

### イテレータの制限
- ループでしか呼び出せない
  - `return`は使えない
- 暗黙的 `result` はない
- 再起できない
- 前方宣言はできない
  - 将来的には前方宣言できるようになる予定




## 組み込みの基本型
- `Boolean`
- `char`
  - 常に`1byte`を表す。
  - `''` で囲んで表現する
  - `$`をつけると `string` に変換できる
  - `ord`関数で数値化できる (`ordinal value`)
- `string`
  - 文字列を表す**ミュータブル**なデータ構造
  - ケツに `$0` が付与されるが、`Nim`の世界では無視される
     - `len`では `$0` はカウントされないし、アクセスするとエラー
     - `$0`があるのは `cstring` にゼロコピーで変換するため。
  - `&` 演算子で文字列の連結、 `add` 演算子で文字列の追加
  - 基本は `UTF-8` エンコーディングだが強制ではない
    - ファイルから読み込み時は単にバイト列として認識されるので注意
- `integer`
  - `int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64`
  - デフォルトは `int` だが`型サフィックス`をつけると指定可能
  - `int` はポインタと同じサイズ（処理系依存？）

```nim
let
  x = 0     # x is of type ``int``
  y = 0'i8  # y is of type ``int8``
  z = 0'i64 # z is of type ``int64``
  u = 0'u   # u is of type ``uint``
```

- `and or xor not` はビット演算子として定義される
  - 左右のビットシフトはそれぞれ `shl, shr`
    - 符号なし操作はすべてラップアラウンドする
    - オーバーフローまたはアンダーフローエラーを引き起こさない
  - コンパイル時にエラーを検出できない場合、型変換によって情報が失われる場合は、`EOutOfRange`例外を投げる

- `float`
  - `float float32 float64`
  - 現状の実装ではデフォルトで`64bit`
  - `IEEE-754`に従った演算
  - `int`から`float`への変換は明示的に行う必要がある
     - `toInt` , `toFloat` を使う
     - `int`同様`型サフィックス`で指定可能

```nim
var
  x = 0.0      # x is of type ``float``
  y = 0.0'f32  # y is of type ``float32``
  z = 0.0'f64  # z is of type ``float64``
```

### repr関数
- `$(stringify)` は実装していない型には使えないが `repr` 関数なら全部の型に使える
    - `print`デバッグに便利









## 一歩進んだ型たち
- `type` で型定義

```nim
type
  biggestInt = int64
  biggestFloat = float64  # biggest float type that is available
```



## 列挙型
- `enum`キーワードを使う
  - 内部的には `0, 1, 2...` の整数
  - `$` なら定義名、 `ord` なら内部の整数を返す

```nim
type
  Direction = enum
    north, east, south, west
 
var x = south     # x は Direction型で値は south
echo x            # writes "south" to `stdout`
```





## 序数型
- 列挙型、整数型、`char`、`bool`、`subrange`のことを序数型と呼ぶ
- 序数型は特別な操作が用意されている


|関数|コメント|
|---|-------|
|ord(x)|x の内部表現（整数）を返す|
|inc(x)|インクリメント|
|inc(x, n)|n の分だけ足す|
|dec(x)|デクリメント|
|dec(x, n)|n の分だけ引く|
|succ(x)|returns the successor of x|
|succ(x, n)|returns the n'th successor of x|
|red(x)|returns the predecessor of x|
|red(x, n)|returns the n'th predecessor of x|




## subranges
- 表現できる範囲に制限をつけた型

```nim
type
  MySubrange = range[0..5] # 0 ~ 5 までの整数のみ代入できる型
```

- `Nim`には`自然数`の型がサブレンジで用意されている

```nim
type Natural range[0..high(int)]
```






## 集合型
- 数学の集合演算を行うための型
- 以下の序数型か内部的にそれに等しい型のみを集合型にできる
  - `int8-int16`
  - `uint8/byte-uint16`
  - `char`
  - `enum`
- 内部実装の制限のため `MaxSetElements` は `0 .. 2^16` まで
  - `var s: set[int64]` はエラーになる
- リテラルは `{ }` で表現
  - 空集合は、どんな型も受け入れる状態

```nim
type
  CharSet = set[char]
var
  x: CharSet
x = {'a'..'z', '0'..'9'} # This constructs a set that contains the
                         # letters from 'a' to 'z' and the digits
                         # from '0' to '9'
```



## ビットフィールド
- 集合型は関数のフラグ定義に使われることがある
- 整数による定数フラグで `or` 演算するよりは型安全になって良い
  - `chown` の `0777` 的なやつ

```nim
# C言語との相互互換を重視するなら cint にする
type
  MyFlag* {.size: sizeof(cint).} = enum
    A
    B
    C
    D
  MyFlags = set[MyFlag]
 
proc toNum(f: MyFlags): int = cast[cint](f)
proc toFlags(v: int): MyFlags = cast[MyFlags](v)
 
assert toNum({}) == 0
assert toNum({A}) == 1
assert toNum({D}) == 8
assert toNum({A, C}) == 5
assert toFlags(0) == {}
assert toFlags(7) == {A, B, C}
```


## 配列
- 配列は固定長、序数型ならなんでもイデックス可能
- リテラルは `[]` で記述する
- 配列のインデックスアクセスのコンパイル時チェックは `--bound_checks:off` オプションで制御可能
- `=` での代入はすべてコピー操作になる。

```nim
type
  IntArray = array[0..5, int] # 0~5までの整数配列
var
  x: IntArray
x = [1, 2, 3, 4, 5, 6]
for i in low(x)..high(x):
  echo x[i]
```



## 多次元配列
- Nimの多次元配列は次元ごとに異なる型でインデクシングが可能
  - よって他の言語と文法が異なる

```nim
type
  Direction = enum
    north, east, south, west
  BlinkLights = enum
    off, on, slowBlink, mediumBlink, fastBlink
  # enum と enum の多次元配列
  LevelSetting = array[north..west, BlinkLights]

var
  level: LevelSetting
level[north] = on
level[south] = slowBlink
level[east] = fastBlink
echo repr(level)  # --> [on, fastBlink, slowBlink, off]
echo low(level)   # --> north
echo len(level)   # --> 4
echo high(level)  # --> west
```







## シーケンス
- 可変長配列で常にヒープに確保されGCの対象になる
  - インデクシングは常に `int` で `0` から始まる
  - `@[]` でシーケンスの配列を初期化する
  - または `newSeq` 関数で初期化
    - なお `@` は配列をシーケンスへ変換する演算子

```nim
var
  x: seq[int] # 整数のシーケンスへの参照
x = @[1, 2, 3, 4, 5, 6] # @を使うと配列をシーケンスにしてヒープへ移動する

# Golang っぽいループとindexが可能
for i, value in @[3, 4, 5]:
  echo "index: ", $i, ", value:", $value
```








## Open Array
- 関数のパラメータとしてのみ使用可能
  - ただしOpenArrayのネストはできない
- 固定長の配列のみではしばしば不便。
- OpenArrayはIntのみでインデクシング、`0`から始まる

```nim
var
  fruits:   seq[string]       # 文字列シーケンス
  capitals: array[3, string]  # 長さ3の固定長配列

capitals = ["New York", "London", "Berlin"]
fruits.add("Banana")
fruits.add("Mango")

# openArray型のパラメータを受け取る関数
proc openArraySize(oa: openArray[string]): int =
  oa.len

assert openArraySize(fruits) == 2     # シーケンス受け取り可能
assert openArraySize(capitals) == 3   # 配列も受取可能
```









## Varargs
- 関数の可変長引数のこと
  - 設置できるのは最後のみ
- 関数の実行時には固定長の配列として扱われる
  - `varargs` 内での型変換も可能

```nim
proc myWriteln(f: File, a: varargs[string, `$`]) =
  for s in items(a):
    write(f, s)
  write(f, "\n")

myWriteln(stdout, 123, "abc", 4.0)
```




## スライス
- Pythonのスライス操作とほぼ同じ
  - 負のIndexを指定するときは `^1` のようにする













## オブジェクト
- いわゆる構造体とかクラスに近いもの
  - 値型なので `=` は全部コピーが走る

```nim
type
  Person = object
    name: string
    age: int

# 自動でコンストラクタ操作可能
# 未指定変数はゼロ値で初期化される
var person1 = Person(name: "Peter", age: 30)
```

- モジュール外へのオブジェクトの公開は `*` で明示する必要がある。

```nim
type
  Person* = object # Person型は外部から使用可能
    name*: string  # 外部モジュールからアクセス可能なメンバ
    age*: int
```







## タプル
- オブジェクトと似ているが、構造的な型をもつ点で異なる
  - 型の並び厳密にあっている必要があり、同じ並びの型のタプルなら同じ型として扱える特徴がある。

```nim
type
  Person = tuple
    name: string
    age: int
  
  # 同じタプル型の別表現
  PersonX = tuple[name: string, age: int]
  
  # 匿名表記
  PersonY = (string, int)

var
  person: Person
  personX: PersonX
  personY: PersonY

person = (name: "Peter", age: 30)
# Person と PersonX は同じ型として扱える
personX = person

# 匿名タプル表記
personY = ("Peter", 30)

# 同じ型と順番で構成されるので匿名タプルでも互換性あり
person = personY
personY = person

# 簡単な表記
person = ("Peter", 30)
echo person.name # "Peter"
echo person.age  # 30
echo person[0] # "Peter"
echo person[1] # 30

# タプル型はどこでも宣言可能
var building: tuple[street: string, number: int]
building = ("Rue del Percebe", 13)
echo building.street

# 以下の代入は型が違うのでだめ！（匿名のPersonYならいけるか？）
#person = building
# --> Error: type mismatch: got (tuple[street: string, number: int])
#     but expected 'Person
```

- 異なるフィールドを持つタプルは型の構造が同じでも別の型として表現される。
- タプルは常にpublicなので外部モジュールからアクセス可能
- タプルは返り値のアンパックにも使えて便利

```nim
import os

let
  path = "usr/local/nimc.html"
  (dir, name, ext) = splitFile(path)
```










## 参照とポインタ型
- N対1の関係を作れる
  - ポインタ操作。同じメモリ領域をいじる
  - なにも参照していない参照型は `nil`
- `Nim` は参照を追跡型と非追跡型で明確に区別している
  - 追跡型の参照点はGC対象のヒープ
    - `ref`キーワードで生成
  - 非追跡型
    - ポインタと呼ぶ
      - `ptr`キーワードで生成
    - 非追跡型参照は手動割当のオブジェクトまたはメモリ内の他の場所のオブジェクトを参照する
    - GC対象外のなのでメモリリークの可能性ありで `unsafe`
      - ハードウェアアクセスなどの低レイヤーでは使用必須
      - 非追跡のアロケーションは `alloc`, `dealloc`, `realloc`関数を使う
- `[]` は `derefer` 操作で、参照先の値を取得する
  - `[]`, `.` によるメンバアクセスは実は `derefer` 操作

```nim
type
  Node = ref object
    le, ri: Node
    data: int
var
  n: Node
new(n)  # 参照先のアロケーションはnew関数が必須
n.data = 9
# n[].data と書く必要はない / n[].data は非推奨！
```



## 手続き型
- いわゆる関数の型
  - `nil`も値として許容される
- "幾分"抽象的なポインタ型として扱う
- 関数型プログラミングのテクニックを使用するときには必要だろう

```nim
proc echoItem(x: int) = echo x

proc forEach(action: proc (x: int)) =
  const
    data = [2, 3, 5, 7, 11]
  for d in items(data):
    action(d)

forEach(echoItem)
```

- 手続き型は、呼び出し規則が型の互換性に影響をあたえることに注意。これは使用上微妙な問題である。
  - 呼び出し規則についてはマニュアルを参照のこと
    - https://nim-lang.org/docs/manual.html#types-procedural-type



## 個別型
- 「その型とその基本型の間のサブタイプの関係を暗示しない」という型の作成する機能
  - `distinct` キーワードを使う
  - すべての振る舞いを記述する必要がある。
  - 詳しくはマニュアルにて
    - https://nim-lang.org/docs/manual.html#types-distinct-type






## モジュール
- ファイル単位がモジュール
- トップレベルのキーワードかつ `*` がついたものだけエクスポートされる
- トップレベルに記述された文はモジュール読み込み時に実行される
- `isMainModule` は `main` としてコンパイルされたら `true`
- 変数は`import`したモジュール間で名前がかぶる場合は `module.variable` という命名規則で区別する必要がある
  - 関数の場合、名前が同じでも手続き型が違ければ名前がかぶってもOK
- `import hoge` は全部インポートする
  - `import mymodule except y` は `y`以外を全部インポートする
- `from mymodule import x, y, z` 形式もあり

```nim
from mymodule as m import nil
m.x()         # m is aliasing mymodule
```

## include文
- C言語 `#include` と同じ
- `include fileA, fileB, fileC` と書く
  - 巨大なモジュールを複数ファイルに分割して書ける