LoginSignup
12
9

More than 1 year has passed since last update.

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

Last updated at Posted at 2019-08-10

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

NimチュートリアルPart1の個人的要点まとめ。
Ver0.20.0

Lexical Element

  • タブ文字はインデントとして識別されない
  • systemモジュールはすべてのファイルに暗黙的にインポートされている
  • ## はドキュメントコメント
    • コメントではあるが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 な処理を入れないとコンパイルできない
 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 プラグマをつかう
 proc p(x, y: int): int {.discardable.} =
   return x + y

for文

  • for のなかの i は自動的に定義されるため事前宣言が不要
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 でブロックから抜けることも可能になる
block myblock:
  var x:int
  ...
  break myblock # ラベルがあるとbreak先が指定可能

when

  • 定数式の比較しかできない
  • if など違い新しい block を生成しない
  • コンパイラは最初に true となったコードしか生成しない
    • 実質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"

インデント

  • Pythonと同様にコードの固まりを表す
  • (and のあとは任意のインデントが可能
# 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言語の参照渡しと同じ効果
# 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++のような関数の多重定義が可能
proc toString(x: int): string = ...
proc toString(x: bool): string =
  if x: result = "true"
  else: result = "false"

演算子

  • 使える文字は基本的に + - * \ / < > = @ $ ~ & % ! ? ^ . |

  • Nim には中置演算子と前置演算子のみ

    • 後置演算子は不可
  • 演算子の定義は使いたい記号を引用符で囲んで関数定義する

proc `$` (x: myDataType): string = ...
# myDataType型で $ 演算子が使用可能になる
  • 演算子は普通の関数のように呼び出し可能
if `==`( `+`(3, 4), 7): echo "True"

前方宣言(Forward Declaration)

  • C言語のプロトタイプ宣言のようなもの
# 前方宣言
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)

イテレータ

  • iteratoryeild キーワードを使う
    • proc と同じくイテレータは多重定義可能
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 はポインタと同じサイズ(処理系依存?)
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同様型サフィックスで指定可能
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 で型定義
type
  biggestInt = int64
  biggestFloat = float64  # biggest float type that is available

列挙型

  • enumキーワードを使う
    • 内部的には 0, 1, 2... の整数
    • $ なら定義名、 ord なら内部の整数を返す
type
  Direction = enum
    north, east, south, west
 
var x = south     # x は Direction型で値は south
echo x            # writes "south" to `stdout`

序数型

  • 列挙型、整数型、charboolsubrangeのことを序数型と呼ぶ
  • 序数型は特別な操作が用意されている
関数 コメント
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

  • 表現できる範囲に制限をつけた型
type
  MySubrange = range[0..5] # 0 ~ 5 までの整数のみ代入できる型
  • Nimには自然数の型がサブレンジで用意されている
type Natural range[0..high(int)]

集合型

  • 数学の集合演算を行うための型
  • 以下の序数型か内部的にそれに等しい型のみを集合型にできる
    • int8-int16
    • uint8/byte-uint16
    • char
    • enum
  • 内部実装の制限のため MaxSetElements0 .. 2^16 まで
    • var s: set[int64] はエラーになる
  • リテラルは { } で表現
    • 空集合は、どんな型も受け入れる状態
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 演算するよりは型安全になって良い
    • chown0777 的なやつ
# 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 オプションで制御可能
  • = での代入はすべてコピー操作になる。
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の多次元配列は次元ごとに異なる型でインデクシングが可能
    • よって他の言語と文法が異なる
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の対象になる
    • インデクシングは常に int0 から始まる
    • @[] でシーケンスの配列を初期化する
    • または newSeq 関数で初期化
      • なお @ は配列をシーケンスへ変換する演算子
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から始まる
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 内での型変換も可能
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 のようにする

オブジェクト

  • いわゆる構造体とかクラスに近いもの
    • 値型なので = は全部コピーが走る
type
  Person = object
    name: string
    age: int

# 自動でコンストラクタ操作可能
# 未指定変数はゼロ値で初期化される
var person1 = Person(name: "Peter", age: 30)
  • モジュール外へのオブジェクトの公開は * で明示する必要がある。
type
  Person* = object # Person型は外部から使用可能
    name*: string  # 外部モジュールからアクセス可能なメンバ
    age*: int

タプル

  • オブジェクトと似ているが、構造的な型をもつ点で異なる
    • 型の並び厳密にあっている必要があり、同じ並びの型のタプルなら同じ型として扱える特徴がある。
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なので外部モジュールからアクセス可能
  • タプルは返り値のアンパックにも使えて便利
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 操作
type
  Node = ref object
    le, ri: Node
    data: int
var
  n: Node
new(n)  # 参照先のアロケーションはnew関数が必須
n.data = 9
# n[].data と書く必要はない / n[].data は非推奨!

手続き型

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

個別型

  • 「その型とその基本型の間のサブタイプの関係を暗示しない」という型の作成する機能

モジュール

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

include文

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9