NimTutorial part1のメモ

はじめに

NimTutorial Part1でのまとめ集(全てではない)です。

目次

内容

Default values

関数の最初の値を決めて置けるというもの。番兵みたいなことができないかな?

演算子

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

これって`で囲うことで通常のプロシージャの動きをさせているんですよね。よってプロシージャのような引数の渡し方ができる!

イテレータ

for文のcountup関数のような、for文内で使用するものをイテレータと言う。返り値がreturn and continueの代わりにyieldになる以外は基本的に変化はない(プロトタイプ宣言が実装されれば…)

boolean・・・while, if, elif, whenで必要
char・・・1バイト(UTF-8の一部に使える)
string・・・終端0にはアクセスできない、またデフォルトはUTF−8
int・・・i8やuint64等もある( = 0`i8という書きかたもある)

数値間の型変換は、型を関数として利用することにより変換が可能

 x: int32 = 1.int32

内部表現

カスタム型などを使用する際は、文字列に変換する$だと処理仕切れない場合がある。その際の解決策がreprプロシージャを代用することである。しかし、$との動きが少し違うことに注意

行動な型

新たな型を定義する際にはtype文を使用する
オブジェクト型と列挙型はtype文の中でのみ定義することができる。

と言うことで、
このtype型を駆使してオブジェクトプログラミングをすることができる
また、C言語の構造体のようなことをしたければ、type文を使う

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

気をつけること
モジュールの外に見えるようにするには、*で明示する必要がある。(おそらく多く使うことになる)

参考になりそうなの
Nimのオブジェクト指向の整理 - Qiita

今後色々調べて正しい情報を探してみる

集合型

数学に置ける集合のような動作をさせることができる

type
  CharSet = set[char]
var
  x: CharSet
x = {'a'..'z', '0'..'9'}

このように定義し、値を入れる。このxを使って集合型同士で演算をすることが可能である(詳しくはTutorialに書いてある)

使用目的はプロシージャのフラグの型を決める時である。

配列

[]で作る

type
  IntArray = array[0..5, int] # an array that is indexed with 0..5
var
  x: IntArray
x = [1, 2, 3, 4, 5, 6]
for i in low(x)..high(x):
  echo x[i]

・配列へのアクセスで境界値チェック(無効にするにはコンパイルオプションの--bound_checks:off
・配列は値型
・代入演算子は配列を丸ごとコピーする

注意
・配列は単純な固定長のコンテナである。
・配列内の要素はすべて同じ型である。
・配列のインデックスはどんな順序型でもよい。

シーケンス型

・可変長 ・ヒープ領域なためGCの対象 ・配列[]の頭に@をつけることで、シーケンス型に変換ができる
・@[]とnilトレードオフの関係である(ヒープ領域の面で)
・for文の範囲にシーケンスを使う時は1、2つの変数を持つことができる
・一変数形式の場合は、変数は配列の値を保持する(systemモジュールのietms()) ・二変数形式の場合は、1つ目はインデックスの役割を、2つ目は値を保持する(systemモジュールのpairs())

オープン配列型

オープン配列型はパラメータのみで使われる
多次元配列には対応していない
ネストすることはできない

配列をプロシージャに渡す場合に色々な大きさの配列に対応しなければならなく問題を解決するものである。

var
  fruits:   seq[string]       # reference to a sequence of strings that is initialized with '@[]'
  capitals: array[3, string]  # array of strings with a fixed size

capitals = ["New York", "London", "Berlin"]   # array 'capitals' allows assignment of only three elements
fruits.add("Banana")          # sequence 'fruits' is dynamically expandable during runtime
fruits.add("Mango")

proc openArraySize(oa: openArray[string]): int =  # パラメータにしか使えないことがわかる
  oa.len

assert openArraySize(fruits) == 2     # ここでシーケンスだけでなく
assert openArraySize(capitals) == 3   # 配列型にも対応している

可変引数

これの目的は、多数の数の引数をプロシージャに渡すことにしようするものらしい(その際はコンパイラーにより配列へと変換される)

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

myWriteln(stdout, "abc", "def", "xyz")
# is transformed by the compiler to:
myWriteln(stdout, ["abc", "def", "xyz"])

このように変換が行われている

他にも、型変換にも使えるそうである

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

myWriteln(stdout, 123, "abc", 4.0)
# is transformed by the compiler to:
myWriteln(stdout, [$123, $"abc", $4.0])

スライス

簡単に言えば(怒られそうだが)配列の部分的な範囲にアクセスする手段となる

var
  a = "Nim is a programming language"
  b = "Slices are useless."

echo a[7..12] # --> 'a prog'
b[11..^2] = "useful"
echo b # --> 'Slices are useful.'

これもまた、公式のソースを見た方がわかりやすかった

他にも^hoge.len-1を使うこともできる

オブジェクト型

いわゆるC言語の構造体
異なる値を名前つきの構造体にまとめることができる

type
  Person = object
    name: string
    age: int

var person1 = Person(name: "Peter", age: 30)

echo person1.name # "Peter"
echo person1.age  # 30

var person2 = person1 # copy of person 1

person2.age += 14

echo person1.age # 30
echo person2.age # 44


# 順番が変えることもできる
let person3 = Person(age: 12, name: "Quentin")

# 全てのメンバーを指定する必要はない
let person4 = Person(age: 3)
# 指定されていないメンバーはデフォルトの値で初期化される
# この場合は空の文字列
doAssert person4.name == ""

定義モジュールの外部から見えるようにするには、オブジェクトフィールドに*をつけなければいけない

type
  Person* = object # 型が他のモジュールから見えるようになる
    name*: string  # このタイプのフィールドは他のモジュールから見えるようになっている
    age*: int

タプル型

タプル型はオブジェクト型と似ているが、フィールドの値から順序まで定義することになる。また、コンストラクタである()を使用することができる

type
  # type representing a person:
  # A person consists of a name and an age.
  Person = tuple
    name: string
    age: int
  
  # Alternative syntax for an equivalent type.
  PersonX = tuple[name: string, age: int]
  
  # anonymous field syntax
  PersonY = (string, int)

var
  person: Person
  personX: PersonX
  personY: PersonY

person = (name: "Peter", age: 30)
# Person and PersonX are equivalent
personX = person

# Create a tuple with anonymous fields:
personY = ("Peter", 30)

# A tuple with anonymous fields is compatible with a tuple that has
# field names.
person = personY
personY = person

# Usually used for short tuple initialization syntax
person = ("Peter", 30)

echo person.name # "Peter"
echo person.age  # 30

echo person[0] # "Peter"
echo person[1] # 30

# You don't need to declare tuples in a separate type section.
var building: tuple[street: string, number: int]
building = ("Rue del Percebe", 13)
echo building.street

# The following line does not compile, they are different tuples!
#person = building
# --> Error: type mismatch: got (tuple[street: string, number: int])
#     but expected 'Person'

()を使う例は以下のコードのように特定の値を代入する場合となる

import os

let
  path = "usr/local/nimc.html"
  (dir, name, ext) = splitFile(path)
  baddir, badname, badext = splitFile(path)
echo dir      # outputs `usr/local`
echo name     # outputs `nimc`
echo ext      # outputs `.html`
# 以下は全て同じ文字列が出力される
# `(dir: usr/local, name: nimc, ext: .html)`
echo baddir
echo badname
echo badext

リファレンスとポインタ型

この2つの違いはGCで管理されるかされないかになっているようです。通常はGCにトレースさせた方が安全であるため、リファレンス型が使用される。しかし、自作OSや組み込みを行う際はGCは組み込まないしハードウェアアクセスをするのにポインタが必要と言うときにポインタ型を使用する。

トレースする参照はref トレースしない参照はptr

と言うキーワードを使用することになる

また、[]はリファレンス型に対する逆参照になるらしい

type
  Node = ref object
    le, ri: Node
    data: int
var
  n: Node
new(n)
n.data = 9
# no need to write n[].data; in fact n[].data is highly discouraged!

トレースされるオブジェクトを新しく作る際はnewプロシージャが必要である。

また、トレースされない場合はalloc,dealloc,reallocといったプロシージャが使える

リファレンス型が何もさしていない場合は、nilを持つ

プロシージャ型

いわゆる関数ポインタ

呼び出し規約が型の適合性に影響するようです。(同じ呼出規約でなければ互換性がない)
呼出規約は以下を参照

Nim Manual

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)

ディスティンクト型

元の型のサブタイプである関係を暗示しない型を新しく作成することができる。

注:明示的に振る舞いを定義する必要がある。

また、元となった型とディスティンクト型は一方から一方にキャストすることができる。

詳しくはManualをとのことらしい
公式

Nim Manual

和訳されている方の該当ページ

Nimマニュアルを日本語訳してみた - Qiita

モジュール

Pythonのimportみたいな(基本的にimport文を使っているのが多い)

モジュール意義は情報隠蔽や分割コンパイルを可能にすること

エクスポートされる時に重要なのが*である。これをつけているトップレベルの識別子のみが他のモジュールにエクスポートされる。

また、各モジュールはisMainModuleと言うマジック定数を持っている。これはモジュールがコンパイルされる時はtrueであるものらしく、これによりテストの埋め込みができる

when isMainModule:
  # test the new ``*`` operator for sequences:
  assert(@[1, 2, 3] * @[1, 2, 3] == @[1, 4, 9])

モジュールでは、例えば同じ名前の変数を二つのモジュールからエクスポートした場合は曖昧になってしまう(エラーをはく)。これを解決するには<モジュール名>.<変数名>とすることでできる。また、エクスポートしたモジュールで新しく同じ名前の変数を宣言した場合はそちらの方が優先される。

# Module A
var x*: string

# Module B
var x*: int

# Module C
import A, B
write(stdout, x) # error: x is ambiguous
write(stdout, A.x) # okay: qualifier used

var x = 4
write(stdout, x) # not ambiguous: uses the module C's x

プロシージャーやイテレータをエクスポートした場合はこの規則でなく、オーバーロードの規則が適用される。

# Module A
proc x*(a: int): string = $a

# Module B
proc x*(a: string): string = $a

# Module C
import A, B
write(stdout, x(3))   # no error: A.x is called
write(stdout, x(""))  # no error: B.x is called

proc x*(a: int): string = discard
write(stdout, x(3))   # ambiguous: which `x` is to call?

除外修飾子

import文を実行する際に除外したい識別子を除外するのに使う

import mymodule except y

from文

列挙した識別子のみをインポートする方法

from mymodule import x, y, z

識別子を修飾するのに使うことができる

通常

from mymodule import x, y, z

x()           # 修飾は必要ない

強制した場合

from mymodule import nil

mymodule.x()  # 修飾があるので問題ない

x()           # 修飾がないのでエラーを起こす

修飾の際の短い識別子を使うことができる

from mymodule as m import nil

m.x()         # mと言う再定義した修飾子を使用している

include文

include文はimport文と根本的に違う動きをする。ただ単にファイルを含めるだけである。(みんなimport文使ってるけど、どうなんだろ…)include文は長いモジュールを分割するのに使える。