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を持つ
プロシージャ型
いわゆる関数ポインタ
呼び出し規約が型の適合性に影響するようです。(同じ呼出規約でなければ互換性がない)
呼出規約は以下を参照
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をとのことらしい
公式
和訳されている方の該当ページ
モジュール
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文は長いモジュールを分割するのに使える。