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


# NimチュートリアルPart2

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


## Pragmas
- コンパイラに付加情報を伝えるための方法
    - `{.  .}` で囲む
    - マニュアル
        - https://nim-lang.org/docs/manual.html#pragmas
    - ユーザーガイド
        - https://nim-lang.org/docs/nimc.html#additional-features







## オブジェクト指向
- 継承
    - 完全にオプショナルな機能
    - 多重継承は現時点では未サポート
- 継承させたいものは`RootObj` を継承している必要がある。
    - `object of`シンタックス
- 継承を伴う型は、厳密には強制されてないが、普通は`ref`としてマークされる。
    - 実行時にオブジェクトが特定の型であるかどうかを確認するには`of`演算子を使用する
- 継承元がない`object`は`final`扱い

```nim
type
  Person = ref object of RootObj
    name*: string 
    age: int
  
  Student = ref object of Person # Student は Person を継承
    id: int                      # id フィールドが追加

var
  student: Student
  person: Person
assert(student of Student) # true
# object の生成
student = Student(name: "Anton", age: 5, id: 2)
echo student[]
```

- 基本的に継承されるオブジェクトは `ref` だが強制ではない
    - ただし `non-ref`な型で `let person: Person = Student(id:123)` とかすると、サブクラスフィールドが全部削ぎ落とされる。



## 相互再起型
- 一つの `type` 宣言のなかで定義する

```nim
type
  Node = ref object  # a reference to an object with the following field:
    le, ri: Node     # left and right subtrees
    sym: ref Sym     # leaves contain a reference to a Sym
  
  Sym = object       # a symbol
    name: string     # the symbol's name
    line: int        # the line the symbol was declared in
    code: Node       # the symbol's abstract syntax tree
```













## 型変換
- 型キャストと型変換は明確に区別する
    - キャストは `cast` 演算子を使う
    - コンパイラは別の型のビットパターンを受け取るようになる
- 型変換はAST的な値を維持したまま変換し内部のbit表現は変化は必須ではない
    - 型変換が不可能な場合、コンパイル警告か例外が発生する
    - `InvalidObjectConversionError`
    - 型変換は `変換したい型名(値)` の形式で行う。






## オブジェクトバリアント
- オブジェクトの継承関係よりバリアントがシンプルで良い場合がある

```nim
# Nim の抽象構文木をどうモデリングするかの例
type
  NodeKind = enum  # AST のノードを表す
    nkInt,          # 整数の AST Node
    nkFloat,        # Float の AST Node
    nkString,       # 文字列の AST Node
    nkAdd,          # 足し算
    nkSub,          # 引き算
    nkIf            # if文
  Node = ref object
    case kind: NodeKind  # ``kind`` フィールド は識別者
    of nkInt: intVal: int
    of nkFloat: floatVal: float
    of nkString: strVal: string
    of nkAdd, nkSub:
      leftOp, rightOp: Node
    of nkIf:
      condition, thenPart, elsePart: Node

var n = Node(kind: nkFloat, floatVal: 1.0)
# n.kindにfitする値ではないので `FieldError`例外が発生する
n.strVal = ""
```

- オブジェクト階層の利点
    - 異なるオブジェクトタイプ間の変換が必要ない
    - 無効なオブジェクトフィールドにアクセスすると例外が発生する
















## メソッド呼び出しのシンタックス
- メソッド呼び出しには糖衣構文が用意されている
    - `method(obj, args)`　→　`obj.method(args)`
    - 引数なしの場合は `()`省略可能で `obj.method` と記述可能
    - この糖衣構文はオブジェクト以外にも適用可能

```nim
import strutils

echo "abc".len  # len("abc") と書くのと同じ
echo "abc".toUpperAscii()
echo({'a', 'b', 'c'}.card)
stdout.writeLine("Hallo") # writeLine(stdout, "Hallo") と同じ
```

- オブジェクト指向のメソッドチェインぽい書き方もできる

```nim
import strutils, sequtils

stdout.writeLine("Give a list of numbers (separated by spaces): ")
stdout.write(stdin.readLine.splitWhitespace.map(parseInt).max.`$`)
stdout.writeLine(" is the maximum!")
```



## プロパティ
- メソッド呼び出しの糖衣構文でオブジェクトプロパティの `getter` は必要ないのでは？となるが
    - しかし `setter` に相当するものはない
    - よって新しい構文が必要になる

```nim
type
  Socket* = ref object of RootObj
    h: int # `*` がついてないので外部に露出していない

proc `host=`*(s: var Socket, value: int) {.inline.} =
  ## hostアドレスへのsetter
  s.h = value

proc host*(s: Socket): int {.inline.} =
  ## hostアドレスのgetter
  s.h

var s: Socket
new s
# 定義した演算子でsetterを実現する
s.host = 34  # same as `host=`(s, 34)
```





## 動的ディスパッチ
- プロシージャは常に `static dispatch`
- 動的ディスパッチは `proc` の代わりに `method` キーワードで定義する

```nim
type
  Expression = ref object of RootObj ## 式のため抽象ベースクラス
  Literal = ref object of Expression
    x: int
  PlusExpr = ref object of Expression
    a, b: Expression

# 'eval' は動的ディスパッチバインディングに頼る
method eval(e: Expression): int {.base.} =
  # この method をオーバーライドする
  quit "to override!"

method eval(e: Literal): int = e.x
method eval(e: PlusExpr): int = eval(e.a) + eval(e.b)

# 以下のコンストラクタは proc で static binding のが理にかなっている
proc newLit(x: int): Literal = Literal(x: x)
proc newPlus(a, b: Expression): PlusExpr = PlusExpr(a: a, b: b)

echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
```

### multi-method
- オブジェクトタイプを持つすべてのパラメーターがディスパッチに使用できる
    - **注意**: `multi-method`使用時はコンパイラに `--multimethods:on` を渡さないとエラー

```nim
type
  Thing = ref object of RootObj
  Unit = ref object of Thing
    x: int

method collide(a, b: Thing) {.inline.} =
  quit "to override!"

method collide(a: Thing, b: Unit) {.inline.} =
  echo "1"

method collide(a: Unit, b: Thing) {.inline.} =
  echo "2"

var a, b: Unit
new a
new b
# (unit, unit) の引数なので (Unit, Thing) が呼び出される
collide(a, b) # output: 2
```

### パフォーマンスの注意
- Nimは仮想methodテーブル(いわゆる`vtable`)を生成しないが、メソッドの呼び出しツリーを生成する
    - メソッド呼び出しの高コストな間接分岐が回避され、インライン化が可能
    - しかし `method` はコンパイル時計算やデッドコード除去など、他の最適化はできない






## 例外処理
- Nimの例外はオブジェクト
    - `system.Exception` から派生する
    - 慣習として`Error`が末尾につく
    - `msg`フィールドで詳細情報を書くのが必須
- 例外は生存期間が不明なので常にヒープ領域に置かれる
- Nimでの慣習として
    - 例外は本当に例外的なときだけ発生させるようにしましょう
    - 例えばFileが開けないときに例外は起きない
        - ファイルがない、というのはよくある一般的な状況で `例外` ではないよね？ということ


### Raise文
- `raise`が単体で使われる場合、前回と同じ例外が投げられる
    - これを避けるため、`system` の以下のテンプレを使う
    - `raise newException(OSError, "the request to the OS failed")`

```nim
var
  e: ref OSError
new(e)
e.msg = "the request to the OS failed"
raise e
```



### Try文

```nim
from strutils import parseInt

# ファイルから2行読み込んで足す処理
var
  f: File
if open(f, "numbers.txt"):
  try:
    let a = readLine(f)
    let b = readLine(f)
    echo "sum: ", parseInt(a) + parseInt(b)
  except OverflowError:
    echo "overflow!"
  except ValueError:
    echo "could not convert string to integer"
  except IOError:
    echo "IO error!"
  except:
    echo "Unknown exception!"
    # reraise the unknown exception:
    raise
  finally:
    close(f)
```

- 例外オブジェクトは`except`で`消費`される
    - `消費`されなかった場合スタックを通じて例外が伝搬する
        - `finally`を除く、残りのプロシージャ部分が実行されなくなる
- 現在の例外オブジェクトにアクセスする
    - `system` の `getCurrentException`, `getCurrentExceptionMsg` を使う

```nim
try:
  doSomethingHere()
except:
  let
    e = getCurrentException()
    msg = getCurrentExceptionMsg()
  echo "Got exception ", repr(e), " with message ", msg
```







### 例外でプロシージャにアノテーションを加える
- `{. raises .}`プラグマを使う
    - このプラグマの指定外の例外が発生するコードがあるとコンパイルが止まる
    - 想定外の変更をコンパイラが通知してくれて便利な場合がある
- `{. effects .}` プラグマを追加すると、そのプロシージャの影響(例外含む)を出力してくれるので例外把握に便利。

```nim
proc complexProc() {.raises: [IOError, ArithmeticError].} =
  ...

proc simpleProc() {.raises: [].} =
  ...
```










## ジェネリクス
- 型安全なコンテナ

```nim
type
  BinaryTree*[T] = ref object # BinaryTree is a generic type with
                              # generic param ``T``
    le, ri: BinaryTree[T]     # left and right subtrees; may be nil
    data: T                   # the data stored in a node

proc newNode*[T](data: T): BinaryTree[T] =
  # constructor for a node
  new(result)
  result.data = data
```














## テンプレート
- NimのASTで動く、かんたんな置換メカニズム
    - テキストのプリプロセスに近い。
    - Nim本家曰く言語とうまく統合され、Cのプリプロセッサマクロより便利

```nim
template `!=` (a, b: untyped): untyped =
  # systemモジュールに実際に定義されている例
  not (a == b)
  assert(5 != 6) # コンパイラが右のように書き換える assert(not (5 == 6))
```

- Nimの`!=, >, >=, in, notin, isnot`は実際にはテンプレート
    - 例えば `==` の演算を定義するとテンプレートにより自動的に `!=` も使えるようになるという利点がある



## テンプレートは遅延評価をしたいときに特に便利
- 以下の例は `debug = false` でも `log()` の呼び出し時の `$` などの処理が走ってしまい、無駄な演算コストがかかる

```nim
const
  debug = true

proc log(msg: string) {.inline.} =
  if debug: stdout.writeLine(msg)

var
  x = 4
log("x has the value: " & $x)
```

- これをテンプレート化（上記コードの `proc` を `template` に変える）すると無駄な処理を省ける
    - `log` の部分が愚直に `if debug: ..` に置き換わるので、引数の評価が実行されない



## テンプレートと型
- `untyped`
    - テンプレートに式が渡される前にシンボル検索と型解決が実行されない
    - ブロックをテンプレートに渡したいときは、最後の引数に `untyped` を追加する
- `typed`
- `type`
    - 型シンボルのみ引数に与えられる

```nim
template withFile(f: untyped, filename: string, mode: FileMode,
                  body: untyped) =
  let fn = filename # 引数の評価が一度だけで済むようにするため
  var f: File
  if open(f, fn, mode):
    try:
      body
    finally:
      close(f)
  else:
    quit("cannot open: " & fn)

withFile(txt, "ttempl3.txt", fmWrite):
  # 以下の2秒は body パラメータに渡される
  txt.writeLine("line 1")
  txt.writeLine("line 2")
```

- 明示的な返り型のないテンプレートは `void` になる


### テンプレートでのプロシージャ上書き

```nim
import math

template liftScalarProc(fname) =
  ## Lift a proc taking one scalar parameter and returning a
  ## scalar value (eg ``proc sssss[T](x: T): float``),
  ## to provide templated procs that can handle a single
  ## parameter of seq[T] or nested seq[seq[]] or the same type
  ##
  ## .. code-block:: Nim
  ##  liftScalarProc(abs)
  ##  # now abs(@[@[1,-2], @[-2,-3]]) == @[@[1,2], @[2,3]]
  proc fname[T](x: openarray[T]): auto =
    var temp: T
    type outType = type(fname(temp))
    result = newSeq[outType](x.len)
    for i in 0..<x.len:
      result[i] = fname(x[i])

liftScalarProc(sqrt)   # make sqrt() work for sequences
echo sqrt(@[4.0, 16.0, 25.0, 36.0])   # => @[2.0, 4.0, 5.0, 6.0]
```








## JavaScript へのコンパイル
- jsへのコンパイルできるコードを書くうえで気にかけること
- `addr`, `ptr` のは JavaScript 上ではセマンティクスが異なる
    - `js`コンパーチブルにしたければ `addr`, `ptr` の使用を避けるのが吉
    - `js`へのコード変換がどのようにされるか把握しているなら使用OK
- `cast[T](x)` は `js`では `(x)` にトランケイトされる
    - 符号付き/符号なしint間のキャストは例外
    - C言語では静的キャストとして動作
- `cstring` は `js`の文字列を意味する。
    - `cstring`はセマンティクス上適切なときのみ使うのが良い慣習
    - バイナリデータ用のバッファとかで`cstring`使うのは良くない