Nimチュートリアル(部1)

翻訳者からのメモ
この翻訳は、 stas3kからのコメントに基づいて行われたもので、Nimチュートリアルの2つの部分を翻訳するように提案しました。 これは私に興味があり、私が理解した限りでは、私はそれらを自分で翻訳しました。 誰かが間違いを見つけた場合(おそらくそこにあります-最後に目が完全に消えました)、PMで知らせてください、私は編集します。



はじめに



「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



コマンドとほとんど同じですが、いくつかの違いがあります。



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)
      
      





イテレータは手順に非常に似ていますが、いくつかの重要な違いがあります。



ただし、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



は、セットの数学的概念をモデル化します。セットの基本型は、特定のサイズの列挙型のみです。つまり、または同等。その理由は、セットが高性能ビットベクトルとして実装されているためです。より大きなセットを宣言しようとすると、エラーが発生します。



 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



があります。これは、モジュールがメインファイルとしてコンパイルされる場合に当てはまります。これは、前の例で示したように、テストモジュールを内部に埋め込むのに非常に便利です。



他のモジュールに依存するモジュールは許可されますが、非常に望ましくありません。この場合、モジュールは依存関係なしでは再利用できないためです。



モジュールのコンパイルアルゴリズムは次のとおりです。





これは、例で最もよく示されます。



 #  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)



All Articles