翻訳者からのメモ
はじめに
「Den Mensch ist doch ein Augentier-シェーン・ディンゲ・ヴンシュ・イヒ・ミール。」(グループ「ラムシュタイン」の歌「モルゲンシュテルン」からの引用。おおよその翻訳:「しかし、人間は大きな目をした獣です。私にはたくさんの美しいものが必要です。」)
これは、 Nimプログラミング言語のチュートリアルです。 変数、型、コマンドなどの基本的なプログラミング概念に精通していることを前提としていますが、詳細な知識は必要ありません。 言語の複雑なニュアンスに関する多数の例については、公式ガイドをご覧ください。 このドキュメントのすべてのコード例は、Nimスタイルガイドに従っています。
最初のプログラム
変更されたhello worldプログラムから始めましょう。
# echo("What's your name? ") var name: string = readLine(stdin) echo("Hi, ", name, "!")
このコードを
greetings.nim
ファイルに保存します。 コンパイルして実行します。
nim compile --run greetings.nim
--run
により、Nimはコンパイル後にファイルを自動的に実行します。 ファイル名の後に引数を追加することにより、コマンドライン経由でプログラムに引数を渡すことができます。
nim compile --run greetings.nim arg1 arg2
一般的に使用されるコマンドとキーには略語があるため、次のように記述できます。
nim c -r greetings.nim
リリースバージョンをコンパイルするには、次のコマンドを使用します。
nim c -d:release greetings.nim
デフォルトでは、Nimコンパイラは多くのランタイムチェックを生成してデバッグを簡素化します。
-d:release
スイッチは、これらのチェックを無効にし、最適化を有効にします。
プログラムの動作はかなり明白なはずですが、構文を説明します。インデントなしで記述されたコマンドは、プログラムの起動時に実行されます。 インデントNimグループチーム。 インデントはスペースのみで、タブは使用できません。
文字列リテラルは二重引用符で囲みます。
var
コマンドは、名前が
name
でタイプが
string
新しい変数を宣言し
string
。その後、
readLine
プロシージャによって返される値が割り当てられます。 コンパイラは
readLine
が文字列を返すことを知っているため、宣言に型を記述する必要はありません(これはローカル型推論と呼ばれます)。 したがって、このオプションも機能します。
var name = readLine(stdin)
これは、Nimで見られる型推論のほとんど唯一の形式であることに注意してください。これは、簡潔さと読みやすさの間の良い妥協点です。
hello worldプログラムには、コンパイラーに既に知られているいくつかの識別子(
echo
、
readLine
など)が含まれています。 これらの組み込みコマンドは、他のモジュールによって暗黙的にインポートされる
system
モジュールで宣言されます。
字句要素
Nimの字句要素をより詳細に検討してください。 他のプログラミング言語と同様に、Nimはリテラル、識別子、キーワード、コメント、演算子、句読点で構成されています。
文字列と文字リテラル
文字列リテラルは二重引用符で囲まれています。 シンボリック-シングルへ。 特殊文字はバックスラッシュ
\
エスケープされます。
\n
は改行、
\t
はタブなどを意味します。 生の文字列リテラルもあります。
r"C:\program files\nim"
生のリテラルでは、バックスラッシュはエスケープ文字ではありません。
文字列リテラルを記述する最後の3番目の方法は、 長い文字列リテラルです。 これらは、三重引用符で囲まれています:
""" ... """
、改行が含まれている場合があり、
\
はエスケープ文字ではありません。 たとえば、コードにHTMLフラグメントを含める場合に非常に便利です。
コメント
コメントは、文字列または文字リテラルの外側の任意の場所に置くことができ、ポンド記号
#
始まります。 ドキュメントのコメントは
##
始まります:
# . var myVariable: int ##
ドキュメントコメントはトークンであり、構文ツリーに含まれているため、入力ファイルの特定の場所でのみ見つけることができます! これにより、ドキュメントジェネレーターが簡素化されます。
長い文字列リテラルで
discard
コマンドを使用して、ブロックコメントを作成することもできます。
discard """ Nim - . yes("May I ask a pointless question?") """
数字
数値リテラルは、他のほとんどの言語と同じ方法で記述されます。 読みやすくするために、数字をアンダースコアで
1_000_000
ことができます:
1_000_000
(100万)。 ピリオド(
e
または
E
いずれか)を含む数値は、浮動小数点数のリテラルと見なされます:
1.0e9
(10億)。 16進リテラルはプレフィックス
0x
で始まり、バイナリは
0b
で、8進数は
0o
始まります。 先行ゼロは 、数値を8進数に変換しません 。
var
コマンド
var
コマンドは、新しいローカル変数またはグローバル変数を宣言します。
var x, y: int # x y, `int`
var
キーワードの後にインデントを使用すると、変数のセクション全体をリストできます。
var x, y: int # a, b, c: string
割り当てコマンド
割り当てコマンドは、変数に、より一般的には保存場所に新しい値を割り当てます。
var x = "abc" # `x` x = "xyz" # `x`
=
これは代入演算子です。 オーバーロード、書き換え、または禁止されていない可能性がありますが、これはNimの将来のバージョンで変更される可能性があります。 1つの代入演算子で複数の変数を宣言でき、それらはすべて同じ値を取得します。
var x, y = 3 # `x` `y` 3 echo "x ", x # "x 3" echo "y ", y # "y 3" x = 42 # `x` 42, `y` echo "x ", x # "x 42" echo "y ", y # "y 3"
プロシージャを呼び出す単一の割り当てで複数の変数を宣言すると、予期しない結果が生じる可能性があることに注意してください。コンパイラは割り当てを拡張し 、最終的に数回プロシージャを呼び出します。 プロシージャの結果が副作用に依存する場合、変数は異なる値を取得できます! これを回避するには、定数値のみを使用します。
定数
定数は、値に関連付けられた文字です。 定数の値は変更できません。 コンパイラは、コンパイル時に定数宣言の式を評価できる必要があります。
const x = "abc" # x "abc"
const
キーワードの後にインデントを使用すると、定数のセクション全体をリストできます。
const x = 1 # , y = 2 z = y + 5 #
let
コマンド
let
コマンドは
var
とほとんど同じように機能しますが、 単一割り当て変数を宣言します。初期化後、それらの値は変更できません。
let x = "abc" # `x` x = "xyz" # : `x`
let
と
const
の違い
const
次のとおりです
const
再割り当てできない変数を導入し、
const
は「コンパイル時にコンパイルを強制し、結果をデータセクションに配置する」ことを意味します。
const input = readLine(stdin) # :
let input = readLine(stdin) #
フロー制御コマンド
ウェルカムプログラムには、順番に実行される3つのコマンドが含まれています。 しかし、この方法で動作できるのは最も原始的なプログラムだけであり、さらに複雑なプログラムではループとブランチが必要です。
if
コマンド
if
コマンドは、実行のスレッドを分岐する1つの方法です。
let name = readLine(stdin) if name == "": echo("Poor soul, you lost your name?") elif name == "name": echo("Very funny, your name is name.") else: echo("Hi, ", name, "!")
elif
ブランチはゼロ以上にすることができ、
else
ブランチはオプションです。
elif
キーワードは、不必要にインデントしない
else if
、
else if
省略形です。 (
""
は空の文字列で、文字は含まれません。)
case
コマンド
別の分岐方法は、
case
コマンドによって実装されます。 実行スレッドをいくつかのブランチに分割します。
let name = readLine(stdin) case name of "": echo("Poor soul, you lost your name?") of "name": echo("Very funny, your name is name.") of "Dave", "Frank": echo("Cool name!") else: echo("Hi, ", name, "!")
ご覧のとおり、コンマで区切られた値のリストをofの引数として使用できます。
case
コマンドは、整数、その他の列挙型、および文字列を処理できます。 (列挙型については後で説明します。)整数および列挙型の場合、値の範囲を使用できます。
# : from strutils import parseInt echo("A number please: ") let n = parseInt(readLine(stdin)) case n of 0..2, 4..7: echo("The number is in the set: {0, 1, 2, 4, 5, 6, 7}") of 3, 8: echo("The number is 3 or 8")
ただし、上記のコードはコンパイルされません。 理由は、
n
が取り得るすべての値をカバーする必要があり、コードは値
0..8
のみを処理するためです。 すべての可能な整数をリストすることはあまり実用的ではないので(これは範囲表記のために可能ですが)、他のすべての値に対して何もする必要がないことをコンパイラに伝えることでこれを修正します。
... case n of 0..2, 4..7: echo("The number is in the set: {0, 1, 2, 4, 5, 6, 7}") of 3, 8: echo("The number is 3 or 8") else: discard
空の
discard
コマンドは、 何もしないコマンドです。 コンパイラーは、
else
セクションの
case
コマンドがすべての可能なオプションをカバーしているため、エラーが消えることを認識しています。 すべての文字列値をカバーすることは不可能であることに注意してください:文字列の場合、
else
ブランチが必要です。
一般に、
case
コマンドは、型または列挙の範囲に使用されます。この
case
、コンパイラがすべての可能な値のカバレッジをチェックするのに役立ちます。
while
コマンド
while
コマンドは単純なループです:
echo("What's your name? ") var name = readLine(stdin) while name == "": echo("Please tell me your name: ") name = readLine(stdin) # `var` ,
この例では、
while
使用して、ユーザーがEnterキーを押す(つまり、空の文字列を入力しない)までユーザーに名前を尋ねます。
コマンド用
for
コマンドは、反復子のすべての要素でループを実装します。 組み込みの
countup
アップイテレータの使用例を次に示します。
echo(" : ") for i in countup(1, 10): echo($i) # --> 1 2 3 4 5 6 7 8 9 10
組み込みの
$
演算子は、整数(
int
)およびその他の多くの型を文字列に変換します。 変数
i
for
ループによって暗黙的に宣言され、型
int
です。これは、
countup
が正確にこの型を返すためです。
i
は値1、2、..、10を通過します。各値は
echo
を使用して表示されます。 このコードは同じことを行います:
echo(" 10: ") var i = 1 while i <= 10: echo($i) inc(i) # i 1 # --> 1 2 3 4 5 6 7 8 9 10
カウントダウンも同様に簡単に実装できます(それほど頻繁には必要ありません):
echo(" 10 1: ") for i in countdown(10, 1): echo($i) # --> 10 9 8 7 6 5 4 3 2 1
拡大でのカウントはプログラムでよく使用されるため、Nimにはイテレータがあります
..
これは
countup
と同じことを行います
..
for i in 1..10: ...
スコープとblock
コマンド
フロー制御チームには、まだ説明されていない機能があります。新しいスコープが開かれます。 これは、次の例で
x
、ループの外側で
x
使用できないことを意味します。
while false: var x = "hi" echo(x) #
while
(
for
)コマンドは、暗黙的なブロックを作成します。 識別子は、宣言されたブロック内でのみ表示されます。
block
コマンドを使用して、新しいブロックを明示的に開くことができます。
block myblock: var x = "hi" echo(x) #
ブロックラベル (この例では
myblock
)はオプションです。
break
コマンド
break
コマンドを使用して、事前にブロックを終了できます。 このコマンドは、
while
、
for
または
block
コマンドブロック
for
中断できます。 終了するブロックのラベルが指定されていない場合、最も近いブロックを終了します。
block myblock: echo(" ") while true: echo("") break # , echo(" ") block myblock2: echo(" ") while true: echo("") break myblock2 # ( ) echo(" ")
continue
コマンド
他の多くのプログラミング言語と同様に、
continue
コマンドは次の反復にすぐに進みます。
while true: let x = readLine(stdin) if x == "": continue echo(x)
when
コマンド
例:
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")
when
コマンドは
if
コマンドとほとんど同じですが、いくつかの違いがあります。
- 計算されるため、各条件は定数式でなければなりません。
- ブランチ内のチームは新しいスコープを開きません
- コンパイラーは構文をチェックし、
true
を返した最初の条件に従ってブランチに属するコマンドのみのコードを生成しtrue
。
when
コマンドは、
#ifdef
C言語に似たプラットフォーム固有のコードを作成するのに役立ちます。
注 :大量のコードをコメントアウトするには、コメントの代わりにwhen false:
コンストラクトを使用する方が便利なwhen false:
あります。 繰り返しネストすることができます。
コマンドとインデント
基本的なフロー制御コマンドについて説明したので、Nimのインデントルールに戻りましょう。
Nimでは、 単純 なコマンドと複雑なコマンドを区別しています。 割り当て、プロシージャコール、
return
などの単純なコマンドに他のコマンドを含めることはできません。
if
、
when
、
for
、
while
などの複雑なコマンドには、他のコマンドを含めることができます。 あいまいさを避けるため、複雑なコマンドは常にインデントされますが、単純なコマンドはそうではありません。
# : if x: x = false # if: if x: if y: y = false else: y = true # , : if x: x = false y = false
通常何らかの意味をもたらすコマンドの部分は、 式と呼ばれます。 読みやすくするために、特定の場所にインデントを含めることができます。
if thisIsaLongCondition() and thisIsAnotherLongCondition(1, 2, 3, 4): x = true
つまり、式の後のインデントは、演算子の後、角かっこの後、およびコンマの後に許可されます。
括弧とセミコロン(
;
)を使用すると、式のみが許可されているコマンドを使用できます。
# fac(4) : const fac4 = (var x = 1; for i in 1..4: x *= i; x)
手続き
例から
echo
や
readLine
などの新しいコマンドを作成するには、 プロシージャの概念が必要です。 (一部の言語では、 メソッドまたは関数と呼ばれます。) Nimでは、
proc
キーワードを使用して新しいプロシージャが定義されます。
proc yes(question: string): bool = echo(question, " (y/n)") while true: case readLine(stdin) of "y", "Y", "yes", "Yes": return true of "n", "N", "no", "No": return false else: echo("Please be clear: yes or no") if yes("Should I delete all your important files?"): echo("I'm sorry Dave, I'm afraid I can't do that.") else: echo("I think you know what the problem is just as well as I do.")
この例は、
yes
と呼ばれるプロシージャを示しています。このプロシージャは、ユーザーに質問し、yesと答えた場合は
true
を返し、noと答えた場合は
false
を返し
true
。
return
コマンドは、プロシージャ(および、それに応じて
while
)を直ちに終了します。 構文
(question: string): bool
は、プロシージャーが
question
という名前のパラメーターを取得し、
string
型を取得し、
bool
型の値を返すことを意味します。
bool
は組み込み型です。使用できる値は
true
と
false
のみです。
if
または
while
コマンドの条件は
bool
型でなければなりません。
ちょっとした用語:この例で
question
、
question
正式にパラメーターと呼ばれ、
"Should I..."
はこのパラメーターで渡される引数と呼ばれます。
結果変数
値を返すプロシージャでは、戻り値を表す
result
変数が暗黙的に宣言されます。 引数なしの
return
コマンドは、単に
return result
省略形です。
result
変数は、
return
コマンドがなくても、プロシージャの終了時に常に返されます。
proc sumTillNegative(x: varargs[int]): int = for i in x: if i < 0: return result = result + i echo sumTillNegative() # 0 echo sumTillNegative(3, 4, 5) # 12 echo sumTillNegative(3, 4 , -1 , 6) # 7
関数の開始時には、
result
変数は常にすでに宣言されているため、たとえば
var result
を使用して再度宣言しようとすると、同じ名前の通常の変数が不明瞭になります。
result
変数は、常にそのタイプのデフォルト値に初期化されます。 したがって、参照されるデータ型は
nil
になるため、必要に応じて手動で初期化する必要があります。
パラメータ
プロシージャの本体では、パラメータは定数です。 デフォルトでは変更できないため、これによりコンパイラーはパラメーターの受け渡しを最も効率的な方法で実装できます。 プロシージャ内で変数が必要な場合、
var
を使用してプロシージャの本体で変数を宣言する必要があります。 パラメータ名のシェーディングが可能であり、時々使用されます:
proc printSeq(s: seq, nprinted: int = -1) = var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) for i in 0 .. <nprinted: echo s[i]
プロシージャが呼び出し側に渡される引数を変更する必要がある場合、
var
パラメーターを使用できます。
proc divmod(a, b: int; res, remainder: var int) = res = a div b # remainder = a mod b # var x, y: int echo(x) divmod(8, 5, x, y) # x y echo(y)
この例では、
res
と
remainder
は
var
パラメーターです。 このようなパラメータはプロシージャによって変更でき、変更は呼び出し元に見えるようになります。 上記の例では、
var
パラメータの代わりに、タプルを返す方が良いことに注意してください。
チームをdiscard
値を返すプロシージャを呼び出し、その呼び出しの結果を無視するには、
discard
コマンドを使用する必要があります。 Nimでは、戻り値を取得して破棄することはできません。
discard yes(" ?")
呼び出されたプロシージャまたはイテレータが
discardable
プラグマで宣言された場合、戻り値は暗黙的に無視できます。
proc p(x, y: int): int {.discardable.} = return x + y p(3, 4) #
コメントセクションで説明されているように、
discard
コマンドを使用してコメントブロックを作成することもできます。
名前付き引数
プロシージャには多くのパラメータがあり、それらの順序を覚えるのは困難です。 これは特に、複雑なデータ型を構築するプロシージャに当てはまります。 そのような場合、どの引数がどのパラメーターに対応するかがより明確になるように、プロシージャーの引数に名前を付けることができます。
proc createWindow(x, y, width, height: int; title: string; show: bool): Window = ... var w = createWindow(show = true, title = "My Application", x = 0, y = 0, height = 600, width = 800)
createWindow
を呼び出すために名前付き引数を使用したので、引数の順序は
createWindow
ではなくなりました。 名前付き引数と名前なし引数を混在させることができますが、これは読みやすさに影響します。
var w = createWindow(0, 0, title = "My Application", height = 600, width = 800, true)
コンパイラーは、各パラメーターが正確に1つの引数を受け取ることを確認します。
デフォルト値
createWindow
プロシージャを使いやすくするには、 デフォルト値、つまり呼び出し元が指定しなかった場合に引数として使用される値を提供する必要があります。
proc createWindow(x = 0, y = 0, width = 500, height = 700, title = "unknown", show = true): Window = ... var w = createWindow(title = "My Application", height = 600, width = 800)
createWindow
を呼び出すとき、デフォルト値と異なる値のみを指定する必要があります。
デフォルト値を持つパラメータの場合、型推論が機能するため、たとえば
title: string = "unknown"
ように記述する必要はありません。
オーバーロードされたプロシージャ
Nimを使用すると、C ++に似たプロシージャをオーバーロードできます。
proc toString(x: int): string = ... proc toString(x: bool): string = if x: result = "true" else: result = "false" echo(toString(13)) # toString(x: int) echo(toString(true)) # toString(x: bool)
(Nimの
toString
は通常
$
演算子によって実装されることを忘れないでください。)コンパイラは
toString
を呼び出すための最も適切なプロシージャを選択します。 オーバーロードされたプロシージャを選択するアルゴリズムがどのように機能するかについては、ここでは説明しません(この問題については、まもなくマニュアルで説明します)。 しかし、それは不快な驚きをもたらさず、かなり単純な統合アルゴリズムに基づいています。 あいまいな呼び出しはエラーメッセージになります。
オペレーター
Nimライブラリはオーバーロードを集中的に使用します。これの理由の1つは、
+
などのすべての演算子がオーバーロードされたプロシージャにすぎないことです。 パーサーでは、 中置 記号
(+ a)
(a + b)
または接頭表記
(+ a)
(a + b)
演算子を使用できます。 中置演算子は常に2つの引数を取り、前置演算子は常に1つの引数を取ります。 後置演算子はあいまいさを招く可能性があるため禁止されています:
a @ @ b
は
(a) @ (@b)
a @ @ b
(a) @ (@b)
または
(a@) @ (b)
意味しますか? Nimには後
(a) @ (@b)
演算子がないため、この式は常に
(a) @ (@b)
意味します。
and
、
or
、
not
などのいくつかの組み込みキーワード演算子に加えて、演算子は常に次の文字で構成されます:
+ - * \ / < > = @ $ ~ & % ! ? ^ . |
+ - * \ / < > = @ $ ~ & % ! ? ^ . |
ユーザー定義の演算子が許可されます。 独自の
@!?+~
演算子の定義を妨げるものは何もありませんが、読みやすさが影響を受ける可能性があります。
オペレーターの優先順位は、最初の文字によって決まります。 詳細はマニュアルに記載されています。
演算子を定義するには、アポストロフィで囲みます。
proc `$` (x: myDataType): string = ... # $ myDataType, $, #
この表記は、演算子をプロシージャとして呼び出すためにも使用できます。
if `==`( `+`(3, 4), 7): echo("True")
事前のお知らせ
各変数、プロシージャなど 使用する前に宣言する必要があります。(その理由は、Nimがサポートするのと同程度にメタプログラミングをサポートする言語のより良いソリューションを見つけるのが難しいためです。)しかし、これは相互に再帰的な手順では行えません。
# : proc even(n: int): bool proc even(n: int): bool proc odd(n: int): bool = assert(n >= 0) # , if n == 0: false else: n == 1 or even(n-1) proc even(n: int): bool = assert(n >= 0) # , if n == 1: false else: n == 0 or odd(n-1)
それ
odd
は依存し
even
、逆もまた同様です。したがって、
even
完全に定義される前にコンパイラに適合しなければなりません。このような事前宣言の構文は単純です
=
。プロシージャの本体をスキップするだけです。
assert
境界条件を追加して、モジュールセクションで後で説明されます。
この言語の将来のバージョンでは、事前の発表の要件はそれほど厳しくないでしょう。
この例は、演算子の本体が、値が暗黙的に返される単一の式で構成される方法も示しています。
イテレータ
退屈なカウントの例に戻りましょう。
echo(" : ") for i in countup(1, 10): echo($i)
countup
そのようなサイクルで使用する手順を書くことは可能ですか?試してみましょう:
proc countup(a, b: int): int = var res = a while res <= b: return res inc(res)
残念ながら、これは機能しません。問題は、プロシージャが値を返すだけでなく、次の反復で戻って作業を続行する必要があることです。この「戻り、続行」はチームと呼ばれ
yield
ます。これで、キーワードを置き換えるだけ
proc
で
iterator
、ここにあります-最初のイテレーター:
iterator countup(a, b: int): int = var res = a while res <= b: yield res inc(res)
イテレータは手順に非常に似ていますが、いくつかの重要な違いがあります。
- 反復子はループからのみ呼び出すことができます
for
。 - イテレータにコマンドを含めることはできません
return
。また、プロシージャにコマンドを含めることはできませんyield
。 - イテレータには暗黙的な変数はありません
result
。 - 反復子は再帰をサポートしていません。
- コンパイラはイテレータをインライン化できる必要があるため、以前にイテレータを宣言することはできません(この制限はコンパイラの将来のバージョンで削除される予定です)。
ただし、
closure
異なる制限のセットを持つイテレーターを使用することもできます。詳細については、「ファーストクラスイテレータ」のドキュメントセクションを参照してください。イテレータは、プロシージャと同じ名前とパラメータを持つことができます。独自の名前空間を持ちます。したがって、イテレータの結果を蓄積
split
し、moduleからのようにシーケンスの形式で返す同じ名前のプロシージャでイテレータをラップする一般的な方法があります
strutils
。
基本タイプ
このセクションでは、基本的な組み込みタイプとそれらで使用可能な操作について詳しく説明します。
ブール値
Nimの論理型が呼び出さ
bool
れ、2つの定義済みの値、
true
およびで構成されます
false
。コマンドでの条件は
while
、
if
、
elif
および
when
タイプを持っている必要があります
bool
。
タイプのために
bool
定義された事業者
not
、
and
、
or
、
xor
、
<
、
<=
、
>
、
>=
、
!=
と
==
。演算子
and
と
or
短縮計算を実行します。例:
while p != nil and p.name != "xyz": # p.name , p == nil p = p.next
キャラクター
Nim の文字タイプはと呼ばれ
char
ます。サイズは1バイトです。したがって、UTF-8文字にすることはできません。その一部のみです。この理由は効率性です。UTF-8はこのために特別に開発されているため、ほとんどの場合、完成したプログラムはUTF-8でデータを正しく処理します。文字リテラルは単一引用符で囲まれます。
記号は、演算子を使用して比較することができ
==
、
<
、
<=
、
>
とします
>=
。演算子はに
$
変換さ
char
れ
string
ます。文字を整数と混在させることはできません。手順を使用して、文字の数値を取得します
ord
。数値から文字への変換は、プロシージャを使用して実行されます
chr
。
行
Nimの文字列値は可変なので、部分文字列を文字列に追加する操作は非常に効率的です。 Nimの行は同時にゼロで終わり、長さフィールドを含みます。文字列の長さは、組み込みプロシージャによって取得できます
len
。長さは、終端のゼロを考慮に入れません。末尾のゼロにアクセスしてもエラーは発生せず、多くの場合、コードが簡素化されます。
if s[i] == 'a' and s[i+1] == 'b': # , i < len(s)! ...
文字列の代入演算子は、文字列をコピーします。演算子
&
を使用して、ストリングを連結し
add
、サブストリングを追加できます。
文字列の比較は辞書式順序で行われます。すべての比較演算子が許可されます。慣例により、すべての行はUTF-8行ですが、これは必須ではありません。たとえば、バイナリファイルから文字列を読み取る場合、文字列はバイトシーケンスである可能性が高くなります。操作
s[i]
とは、文字列の
i
i番目の文字(
i
n番目のUnicode文字ではない)を意味します
s
。
文字列変数はと呼ばれる特別な値で初期化され
nil
ます。ただし、ほとんどの文字列操作は
nil
(これにより、例外が発生します)パフォーマンス上の理由から。代わりに
nil
、空の文字列を空の値として使用します
""
。ただし
""
、ヒープ上に文字列オブジェクトが作成されるため、ここではメモリとパフォーマンスの妥協点を見つける必要があります。
整数
ニムは、組み込みの整数型の次があります
int
、
int8
、
int16
、
int32
、
int64
、
uint
、
uint8
、
uint16
、
uint32
と
uint64
。
デフォルトは整数型
int
です。整数リテラルには、1つまたは別の整数型に属することを示す接尾辞を付けることができます。
let x = 0 # x int y = 0'i8 # y int8 z = 0'i64 # z int64 u = 0'u # u uint
ほとんどの場合、整数はメモリに格納されているオブジェクトをカウントするために使用されるため、サイズ
int
はポインターのサイズと等しくなります。
大手事業者
+
、
-
、
*
、
div
、
mod
、
<
、
<=
、
==
、
!=
、
>
および
>=
整数のために定義されました。演算子
and
、
or
、
xor
および
not
整数に対して定義され、ビット演算を実行するように。左ビットシフトは演算子を使用して行われ
shl
、右シフトは演算子を使用して行われ
shr
ます。ビットシフト演算子は、常に引数を符号なしの数値として扱います。乗算または除算に使用できます。
すべての署名されていない操作にはラッパーが装備されているため、オーバーフローエラーが発生することはありません。
自動型変換は、異なる整数型を使用する式で実行されます。ただし、型変換によって情報が失われると、例外が発生します
EOutOfRange
(コンパイル段階でエラーが検出されなかった場合)。
浮動小数点数
ニムは内蔵の浮動小数点数の種類以下があります
float
、
float32
と
float64
。
デフォルトでは、タイプが使用され
float
ます。現在の実装で
float
は、サイズは常に64ビットです。
浮動小数点リテラルには、1つまたは別のタイプの浮動小数点数に属することを示す接尾辞を付けることができます。
var x = 0.0 # x float y = 0.0'f32 # y float32 z = 0.0'f64 # z float64
主要オペレーター
+
、
-
、
*
、
/
、
<
、
<=
、
==
、
!=
、
>
および
>=
IEEE規格にポイント番号と対応してフローティングために定義されました。
さまざまなタイプの浮動小数点型を持つ式の自動型変換が実行されます。小さな型は大きな型に変換されます。整数型は浮動小数点型に自動的に変換されず、その逆も行われません。このような変換には、
toInt
およびプロシージャを使用できます
toFloat
。
型変換
Nimのベースタイプ間の変換は、タイプを関数として使用することで実現されます。
var x: int32 = 1.int32 # , int32(1) y: int8 = int8('a') # 'a' == 97'i8 z: float = 2.5 # int(2.5) 2 sum: int = int(x) + int(y) + int(z) # sum == 100
型の内部表現
前述したように、文字列への組み込みの変換演算子は、
$
任意の基本型を、プロシージャを使用して表示できる文字列に変換します
echo
。ただし、オペレーター
$
は、複雑なタイプまたは自分で作成したタイプを、再定義するまで操作できません。
時々、デバッグ時に、別の演算子を記述することなく、複合型の値を見つけることが必要になります
$
。この場合、
repr
任意のタイプ、さらには複雑な循環データグラフでも機能する手順を使用できます。次の例は、基本タイプであっても、結果
$
との間に違いがあることを示しています
repr
。
var myBool = true myCharacter = 'n' myString = "nim" myInteger = 42 myFloat = 3.14 echo($myBool, ":", repr(myBool)) # --> true:true echo($myCharacter, ":", repr(myCharacter)) # --> n:'n' echo($myString, ":", repr(myString)) # --> nim:0x10fa8c050"nim" echo($myInteger, ":", repr(myInteger)) # --> 42:42 echo($myFloat, ":", repr(myFloat)) # --> 3.1400000000000001e+00:3.1400000000000001e+00
追加の種類
Nimの新しいタイプは、次のコマンドを使用して定義できます
type
。
type biggestInt = int64 # , , biggestFloat = float64 # , ,
列挙とオブジェクトタイプは、コマンドでのみ「オンザフライ」で定義できません
type
。
乗り換え
列挙のタイプに関連する変数は、限られた値のセットのみを取ることができます。このセットは、順序付けられた文字で構成されています。各文字は内部的に整数値にマップされます。最初の文字は数字の0に対応し、2番目の文字は数字の1に対応します。例:
type Direction = enum north, east, south, west var x = south # `x` `Direction`; `south` echo($x) # "south" `stdout`
転送には、任意の比較演算子を使用できます。
シンボルを転送するのを回避するために分類できます
Direction.south
。
演算子
$
は、列挙の値を自分の名前に変換し、プロシージャ
ord
を対応する整数値に変換できます。
他のプログラミング言語とのより良い対話のために、列挙文字に整数値を明示的に割り当てることができます。ただし、いずれにしても、昇順でなければなりません。明示的に数値が割り当てられていない文字は、前の文字に1を加えた値を受け取ります。
明示的に番号付けされた列挙には、省略が含まれる場合があります。
type MyEnum = enum a = 2, b = 4, c = 89
列挙型
休憩なしで転送し、整数型
char
、
bool
(およびそのサブバンド) -彼らは列挙型と呼ばれています。列挙型には、いくつかの特別な操作があります。
運営 | 解説 |
---|---|
ord(x)
| 値を表すために使用される整数を返します x
|
inc(x)
| x
1 ずつ増加 |
inc(x, n)
| 増加x
に n
; n
整数です |
dec(x)
| x
1 減少 |
dec(x, n)
| によって減少x
し n
ます。 n
整数です |
succ(x)
| 次のx
アイテムを返します |
succ(x, n)
| n
次の要素を返します x
|
pred(x)
| 先行を返します x
|
pred(x, n)
| n
先行を返します x
|
操作は
inc
、
dec
、
succ
および
pred
エラーで実行することができ、例外がスローされます
EOutOfRange
か
EOverflow
。(もちろん、例外チェックを有効にしてコードをコンパイルしない限り。)
範囲
この型は、整数型または列挙型(基本型)の値の範囲です。 例:
type Subrange = range[0..5]
Subrange
これは、
int
0〜5の値を含むことができる範囲です。他の値を型変数に割り当てる
Subrange
と、コンパイルエラーまたは実行時エラーが発生します。基本タイプをその範囲の1つに(およびその逆に)割り当てることは許可されています。
モジュール
system
は、重要なタイプ
Natural
を
range[0..high(int)]
(
high
許可される最大値を返します)として定義します。他のプログラミング言語では、自然数を扱うために符号なし整数を使用する必要があります。これはしばしば間違っています。数字を負にできないという理由だけで、符号なし算術の使用を強制されるべきではありません。
Natural
Nim言語タイプは、この一般的なプログラミングエラーを回避します。
多くの
タイプ
set
は、セットの数学的概念をモデル化します。セットの基本型は、特定のサイズの列挙型のみです。つまり、
-
int8-int16
-
uint8/byte-uint16
-
char
-
enum
var s: set[int64] # Error: set is too large
セットは、セットコンストラクターを使用して構築できます。
{}
これは空のセットです。空のセットは、特定のタイプのセットとタイプ互換性があります。コンストラクターを使用して、要素(および要素の範囲)を含めることもできます。
type CharSet = set[char] var x: CharSet x = {'a'..'z', '0'..'9'} # , # 'a' 'z' '0' '9'
セットでは、次の操作がサポートされています。
運営 | 説明 |
---|---|
A + B
| 2つのセットの結合 |
A * B
| 2つのセットの交差点 |
A - B
| 2つのセットの差(A
要素なし B
) |
A == B
| セットの平等 |
A <= B
| サブセット関係(A
サブセット B
または同等物 B
) |
A < B
| 厳密なサブセット関係(A
サブセットです B
) |
e in A
| セットのメンバーシップ(A
要素を含む e
) |
e notin A
| A
要素を含まない e
|
contains(A, e)
| A
要素を含む e
|
card(A)
| パワーA
(の要素数 A
) |
incl(A, elem)
| と同じ A = A + {elem}
|
excl(A, elem)
| と同じ A = A - {elem}
|
多くの場合、手順フラグにスコアが使用されます。これは、操作で結合する必要がある整数定数を定義するよりも透明性の高い(タイプセーフな)ソリューションです
or
。
配列
配列は、固定サイズの単純なコンテナです。すべての要素は同じ型です。任意の列挙型を配列インデックスとして使用できます。
配列は次を使用して作成できます
[]
。
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])
表記は
x[i]
、
i
i番目の要素にアクセスするために使用されます
x
。配列要素にアクセスする場合、(コンパイル時または実行時に)常に境界がチェックされます。このチェックは、プラグマによって、またはキーを使用してコンパイラを呼び出すことによって無効にできます
--bound_checks:off
。
配列は、他のNim型と同様に値型です。代入演算子は、配列の内容全体をコピーします。
組み込みプロシージャ
len
は、配列の長さを返します。
low(a)
配列の可能な最小のインデックス
a
と、
high(a)
可能な最大のインデックスを返します。
type Direction = enum north, east, south, west BlinkLights = enum off, on, slowBlink, mediumBlink, fastBlink 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
通常、各次元は他の次元と同じ型である必要があるため、他の言語のネストされた配列(複数の次元)の構文は、角括弧を追加することになります。 Nimでは、異なるインデックスタイプで異なるディメンションを使用できるため、ネスト構文はわずかに異なります。
level
別の列挙によってインデックスが付けられた列挙の配列として定義されている前の例に基づいて、次の行を追加して、ビーコンのタイプを整数インデックスからアクセスできるレベルに分割できるようにします。
type LightTower = array[1..10, LevelSetting] var tower: LightTower tower[1][north] = slowBlink tower[1][east] = mediumBlink echo len(tower) # --> 10 echo len(tower[1]) # --> 4 echo repr(tower) # --> [[slowBlink, mediumBlink, ... .... # - #tower[north][east] = on #tower[0][1] = on
組み込みプロシージャ
len
は、最初のレベルの配列の長さのみを返すことに注意してください。ネストされた性質をよりよく示す
LightTower
ために、前の型定義を記述できませんでした
LevelSetting
が、代わりに最初の次元の型に直接含めることができました。
type LightTower = array[1..10, array[north..west, BlinkLights]]
多くの場合、配列はゼロから始まります。そのため、範囲を0から指定されたインデックスから1を引いた値に設定するための簡単な構文があります。
type IntArray = array[0..5, int] # , 0 5 QuickArray = array[6, int] # , 0 5 var x: IntArray y: QuickArray x = [1, 2, 3, 4, 5, 6] y = x for i in low(x)..high(x): echo(x[i], y[i])
シーケンス
シーケンスは配列に似ていますが、実行時に変更できるのは長さだけです(文字列と同様)。シーケンスはサイズを変更できるため、常にヒープに配置され、ガベージコレクションに関与します。
シーケンスは常にインデックスさ
int
0の操作で始まり
len
、
low
および
high
シーケンスのために便利です。表記
x[i]
を使用して、
i
i番目の要素にアクセスできます
x
。
配列は
[]
、配列から配列への演算子に接続された配列コンストラクターを使用して構築できます
@
。シーケンスにメモリを割り当てるもう1つの方法は、組み込みプロシージャを呼び出すこと
newSeq
です。
シーケンスはパラメータで渡すことができます
openarray
。
例:
var x: seq[int] # x = @[1, 2, 3, 4, 5, 6] # @ ,
シーケンス変数は値で初期化されます
nil
。ただし、シーケンス上のほとんどの操作は
nil
、パフォーマンス上の理由で機能しません(これにより例外がスローされます)。それで、空値として空のシーケンスを使用するのは賢明であるより賢明
@[]
です
nil
。ただし
@[]
、ヒープ上にシーケンスオブジェクトが作成されるため、特定のケースに適したソリューションを探す必要があります。シーケンスに使用される
コマンド
for
は、1つまたは2つの変数で機能します。 1つの変数を持つフォームを使用する場合、変数にはシーケンスによって提供される値が含まれます。チーム
for
は、
items()
モジュール反復子から得られた結果に従います
system
。ただし、2つの変数を持つフォームを使用する場合、最初の変数には位置インデックスが含まれ、2番目の変数には値が含まれます。この場合、コマンドはmoduleの
for
反復子の結果に従い
pairs()
ます
system
。例:
for i in @[3, 4, 5]: echo($i) # --> 3 # --> 4 # --> 5 for i, value in @[3, 4, 5]: echo("index: ", $i, ", value:", $value) # --> index: 0, value:3 # --> index: 1, value:4 # --> index: 2, value:5
オープンアレイ
注:オープン配列は、パラメーターとしてのみ使用できます。多くの場合、固定サイズの配列は十分な柔軟性を持たないことが判明しています。プロシージャは、異なるサイズの配列を処理する必要がある場合があります。これにはオープンアレイタイプがあります。オープン配列は常に整数でインデックス化されており、ナンバリングは彼らのために0で使用可能な操作を開始し
len
、
low
そして
high
。互換性のあるベース型を持つ配列は、オープン配列パラメーターとして渡すことができます;インデックス型は関係ありません。
var fruits: seq[string] # , # 'nil' capitals: array[3, string] # fruits = @[] # , # 'fruits' capitals = ["New York", "London", "Berlin"] # 'capitals' # fruits.add("Banana") # 'fruits' # fruits.add("Mango") proc openArraySize(oa: openArray[string]): int = oa.len assert openArraySize(fruits) == 2 # # assert openArraySize(capitals) == 3 #
オープン配列のタイプをネストすることはできません。多次元オープン配列の必要性はめったに発生せず、効率的に実装できないため、多次元オープン配列はサポートされていません。
任意の数の引数を持つパラメーター
パラメーター
varargs
は、開いている配列に似ています。ただし、さらに、任意の数の引数をプロシージャに渡すことができます。コンパイラーは、引数リストを自動的に配列に変換します。
proc myWriteln(f: File, a: varargs[string]) = for s in items(a): write(f, s) write(f, "\n") myWriteln(stdout, "abc", "def", "xyz") # : myWriteln(stdout, ["abc", "def", "xyz"])
この変換は、パラメーターが
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) # : myWriteln(stdout, [$123, $"abc", $4.0])
この例
$
は、パラメーターを介して渡されるすべての引数に適用されます
a
。
$
文字列に適用しても何も行われないことに注意してください。
スライス数
構文スライスは範囲タイプに似ていますが、異なるコンテキストで使用されます。スライス-それはタイプの単なるオブジェクトの
Slice
2つの境界が含まれ、
a
そして
b
。スライス自体はあまり有用ではありませんが、他のタイプのコレクションは
Slice
、範囲を設定するオブジェクトを受け入れる演算子によって定義されます。
var a = "Nim is a progamming language" b = "Slices are useless." echo a[7..12] # --> 'a prog' b[11..^2] = "useful" echo b # --> 'Slices are useful.'
前の例では、文字列のフラグメントを変更するためにスライスが使用されています。スライスの境界には、そのタイプでサポートされている任意の値を含めることができますが、この値を受け入れるかどうかは、スライスオブジェクトを使用するプロシージャのみが決定します。
タプル
タプルのタイプは、異なる名前のフィールドとこれらのフィールドの順序を定義します。タプルを構築するには、コンストラクターを使用できます
()
。コンストラクターのフィールド順序は、タプル定義のフィールド順序と一致する必要があります。異なるタイプのタプルは、同じ名前の同じタイプのフィールドを同じ順序で指定する場合、同等と見なされます。
タプルの割り当て演算子は、各コンポーネントをコピーします。
t.field
タプルフィールドへのアクセスには表記法が使用されます。別の表記法
t[i]
は、
i
-thフィールドへのアクセスを提供します(
i
整数定数でなければなりません)。
type Person = tuple[name: string, age: int] # , # var person: Person person = (name: "Peter", age: 30) # , : 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) # , ! #person = building # --> Error: type mismatch: got (tuple[street: string, number: int]) # but expected 'Person' # , . var teacher: tuple[name: string, age: int] = ("Mark", 42) person = teacher
タプルを使用するために型を宣言する必要はありませんが、異なるフィールド名で作成されたタプルは、同じフィールド型を持つ場合でも、異なるオブジェクトと見なされます。
タプルは、変数を割り当てるプロセスでアンパックできます(この場合のみ!)。これは、タプルフィールド値を個々の名前付き変数に直接割り当てるのに便利です。例として、ディレクトリ、名前、およびパス拡張子の両方を返す
splitFile
モジュールのプロシージャを考え
os
ます。タプルを適切にアンパックするには、タプルをアンパックする値をカッコで囲む必要があります。そうでない場合は、これらの各変数に同じ値を割り当てます!例:
import os let path = "usr/local/nimc.html" (dir, name, ext) = splitFile(path) baddir, badname, badext = splitFile(path) echo dir # usr/local echo name # nimc echo ext # .html # : # `(dir: usr/local, name: nimc, ext: .html)` echo baddir echo badname echo badext
タプルのアンパックは、ブロック
var
またはでのみ機能します
let
。次のコードはコンパイルされません。
import os var path = "usr/local/nimc.html" dir, name, ext = "" (dir, name, ext) = splitFile(path) # --> Error: '(dir, name, ext)' cannot be assigned to
参照タイプとポインター
参照(他のプログラミング言語のポインターと同じ)は、多対1の関係を整理する方法です。これは、異なるリンクがメモリ内の同じ場所を指し、変更できることを意味します。
Nimは、トレースされたリンクとトレースされていないリンクを区別します。追跡されていないリンクは、ポインターとも呼ばれます。追跡されたリンクは、ガベージコレクションでヒープ上のオブジェクトを指し、追跡されていないリンクは、メモリが手動で割り当てられたオブジェクトまたは他のメモリ位置のオブジェクトを指します。したがって、追跡されていないリンクは安全ではありません。ただし、一部の低レベル操作(ハードウェアへのアクセス)については、それらなしでは実行できません。
追跡されたリンクは、キーワードによって宣言され、追跡されません
ref
-キーワードによって
ptr
。
空の添字表記
[]
を使用して、リンクの逆参照、つまりリンクが指す要素を取得できます。演算子
.
(タプル/オブジェクトフィールドへのアクセス)および
[]
(配列/行/シーケンスインデックス演算子)は、参照型の暗黙的な逆参照を実行します。
type Node = ref NodeObj NodeObj = object le, ri: Node data: int var n: Node new(n) n.data = 9 # n[].data, !
組み込みプロシージャを使用して、新しい監視対象オブジェクトにメモリを割り当てます
new
。作業手順に追跡不可能なメモリを使用することができ
alloc
、
dealloc
そして
realloc
。モジュールのドキュメントに
system
は、これらの問題に関する追加情報が含まれています。
リンクが何かを指していない場合、それは重要
nil
です。
手続き型
手続き型は、手続きへの(やや抽象的な)ポインタです。手続き型変数はvalueを取ることができます
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)
手続き型の非自明な問題は、手続き呼び出し規則が型の互換性に影響することです。手続き型は、同じ呼び出し規則を使用する場合にのみ互換性があります。さまざまな呼び出し規約がマニュアルに記載されています。
モジュール
Nimは、モジュール性の概念に従ってプログラムを分割することをサポートしています。各モジュールは個別のファイルにあります。モジュールを使用すると、情報の非表示と個別のコンパイルを実行できます。コマンドを使用して、他のモジュールのシンボルにアクセスできます
import
。エクスポートできるのは、アスタリスク(
*
)でマークされた最上位のシンボルのみです。
# A var x*, y: int proc `*` *(a, b: seq[int]): seq[int] = # : newSeq(result, len(a)) # : for i in 0..len(a)-1: result[i] = a[i] * b[i] when isMainModule: # ``*`` : assert(@[1, 2, 3] * @[1, 2, 3] == @[1, 4, 9])
モジュールはを
A
エクスポート
x
します
*
が、ではありません
y
。
モジュールのトップレベルのコマンドは、プログラムの起動時に実行されます。これは、たとえば、複雑なデータ構造を初期化するために使用できます。
各モジュールには特別なマジック定数
isMainModule
があります。これは、モジュールがメインファイルとしてコンパイルされる場合に当てはまります。これは、前の例で示したように、テストモジュールを内部に埋め込むのに非常に便利です。
他のモジュールに依存するモジュールは許可されますが、非常に望ましくありません。この場合、モジュールは依存関係なしでは再利用できないためです。
モジュールのコンパイルアルゴリズムは次のとおりです。
- コマンドを再帰的に繰り返しながら、通常どおりモジュールをコンパイルします
import
。 - ループが検出された場合、すでに解析された文字のみをインポート(エクスポート)します。不明な識別子が見つかった場合、中止します。
これは、例で最もよく示されます。
# A type T1* = int # A ``T1`` import B # B proc main() = var i = p(3) # , B main()
# B import A # A ! # , A . proc p*(x: A.T1): A.T1 = # , T1 A result = x + 1
モジュール文字は、構文を使用して修飾できます
module.symbol
。シンボルがあいまいな場合、修飾する必要があります。シンボルが2つ(またはそれ以上)の異なるモジュールで定義され、両方のモジュールが3番目のモジュールによってインポートされる場合、シンボルはあいまいです。
# A var x*: string
# B var x*: int
# C import A, B write(stdout, x) # : x write(stdout, Ax) # : var x = 4 write(stdout, x) # : x C
ただし、この規則はプロシージャまたはイテレータには適用されません。オーバーロードルールはここに適用されます。
# A proc x*(a: int): string = $a
# B proc x*(a: string): string = $a
# C import A, B write(stdout, x(3)) # : Ax write(stdout, x("")) # : Bx proc x*(a: int): string = nil write(stdout, x(3)) # : `x` ?
キャラクターの除外
通常、コマンド
import
はエクスポートされたすべての文字を受け取ります。これは、修飾子で除外文字を指定することで変更できます
except
。
import mymodule except y
コマンドから
import
エクスポートされたすべての文字をインポートする簡単なコマンドを見てきました。次のコマンドを使用すると、リストされている文字のみをインポートできます
from import
。
from mymodule import x, y, z
コマンド
from
は、使用可能な文字の結果として、文字のネームスペースを強制することもできますが、それらを使用するには、修飾子を指定する必要があります。
from mymodule import x, y, z x() # x
from mymodule import nil mymodule.x() # x, x() # x
モジュール名は通常非常に長いため、修飾で使用される短いエイリアスを指定することもできます。
from mymodule as m import nil mx() # m mymodule
コマンドを含める
チーム
include
は、モジュールのインポートとは根本的に異なることを行います。ファイルの内容をその場所に挿入します。このコマンドは、大きなモジュールをいくつかのファイルに分割するのに非常に便利です。
include fileA, fileB, fileC
(続きを追加:「Nimチュートリアル(パート2)。」)