Nimについて (I)

初めて

人間は視覚的な動物です。良いことを期待しています。

この記事は、プログラミング言語Nimのチュートリアルです。このチュートリアルは、基礎的な変数、型、ステートメントなどの基本的なプログラミングの概念に精通していることを前提としています。manualには、いろいろな高度な機能の実例が含まれています。このチュートリアルのコード、およびその他のNimドキュメントは、Nim style guideに従います。

最初のプログラム

まず、調整された「hello world」プログラムから始めます。

# これはコメントです
echo "What's your name? "
var name: string = readLine(stdin)
echo "Hi, ", name, "!"

ファイル「greetings.nim」に保存し、コンパイルして実行します。

nim compile --run greetings.nim

--run switch Nimを使用して、コンパイル後にファイルを自動的に実行します。ファイル名の後にコマンドラインパラメータnimcompile –run Greetings.nim arg1arg2をプログラムに追加できます。

よく使用されるコマンドとスイッチには略語があるため、以下の文字を使用できます。

nim c -r greetings.nim

リリースバージョンをコンパイルするには、以下の文字を使用します。

nim c -d:release greetings.nim

Nimコンパイラは、デバッグを容易にするために、デフォルトで多数のランタイムチェックを生成します。 -d:releaseによって、いくつかのチェックをオフにし、最適化をオンにします。(最近のバージョンでは–d:releaseの機能が変更されました。ランタイムチェックがオンになります。代わりに-d:dangerを使用して、パフォーマンスの高いコードを生成します。)

次には、以下の構文を説明します。インデントされていないステートメントは、プログラムが開始する時に実行されます。インデントは、Nimステートメントがグループ化される方法です。インデントにはスペースのみが許可され、タブは許可されません。

文字列リテラルは二重引用符で囲まれています。varステートメントは、変数名がname、型が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 ## ドキュメントコメント

ドキュメントコメントはトークンです。構文ツリーに属しているため、入力ファイルの特定の場所でのみ許可されます。この機能により、よりシンプルなドキュメントジェネレータが可能になります。

複数行コメントは#[ で始まり、]#で終わります。 複数行コメントもネストできます。

#[
You can have any Nim code text commented
out inside this with no indentation restrictions.
      yes("May I ask a pointless question?")
  #[
     Note: these can be nested!!
  ]#
]#

長い文字列リテラルを含むdiscardステートメントを使用して、ブロックコメントを作成することもできます。

discard """ You can have any Nim code text commented
out inside this with no indentation restrictions.
      yes("May I ask a pointless question?") """

数値

数値リテラルは、その他のほとんどの言語と同じです。特別な場所として、読みやすくするために、下線が許可されています:1_000_000(100万)。ドット(または「e」または「E」)を含む数値は、浮動小数点リテラルです:1.0e9(10億)。16進数のリテラルプレフィックスは0x、2進数のリテラルは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`に新しい値を割り当てる

=は代入演算子です。代入演算子はオーバーロードされる可能性があります。1つの割り当てステートメントで複数の変数を宣言でき、すべての変数は同じ型です。

var x, y = 3  # 変数 `x`と` y`に3を割り当てる
echo "x ", x  #  "x 3"を出力する
echo "y ", y  #  "y 3"を出力する
x = 42        # `y`を変更せずに` x`を42に変更する
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`に値を割り当てる

letconstの違いは、以下のとおりです。letは、再割り当てできない変数を導入します。constは、「コンパイル時の評価を強制し、データセグメントに入れる」ことを意味します。

const input = readLine(stdin) # エラー:定数式が必要である
let input = readLine(stdin)   # できる

フロー制御ステートメント

greetingsプログラムは、順番に実行される3つのステートメントで構成されます。最も原始的なプログラムだけが分岐とループなしで実行できます。

If文

if分はブランチフロー制御のメソッドです。

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の省略形であり、過度のインデントを回避します。 ( ""は空の文字列であり、文字は含まれていません。)

Caseステートメント

分岐するもう1つのメソッドは、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, "!"

ご覧のとおり、ブランチにはコンマ区切りの値のリストが許可されています。

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文は、列挙されたサブ範囲の型に使用されます。コンパイラは、可能な値をカバーしたかどうかを確認するのに非常に役立ちます。

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文

for文は、イテレーターによって提供される要素をループする構造です。この実例では、組み込みのcountupイテレータを使用しています。

echo "Counting to ten: "
for i in countup(1, 10):
  echo i
# --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines

変数iは、countupによって返されるため、forループを介して暗黙的に宣言され、int型を持ちます。iは1、2、..、10をトラバースし、各値がエコーされます。このコードの効果は同じです。

echo "Counting to 10: "
var i = 1
while i <= 10:
  echo i
  inc(i) # increment i by 1
# --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines

カウントダウンは簡単に実行できます。(多くの場合は必要ありません)

echo "Counting down from 10 to 1: "
for i in countdown(10, 1):
  echo i
# --> Outputs 10 9 8 7 6 5 4 3 2 1 on different lines

カウントはプログラムによく表示されますが、Nimには..イテレータの機能は同じです。

for i in 1..10:
  ...

高いインデックスの前のビットへのカウントを単純化するために、ゼロインデックスカウント.. <と.. ^の2つの省略形があります。

for i in 0..<10:
  ...  # 0..9
or
var s = "some string"
for i in 0..<s.len:
  ...

その他の便利なイテレータ(配列やシーケンスなど)は以下のとおりです。

  • itemsmitemsは、不変または変更可能な要素を提供します。
  • pairsmpairsは、要素とインデックス番号を提供します。
for index, item in ["a","b"].pairs:
  echo item, " at index ", index
# => a at index 0
# => b at index 1

スコープステートメントとブロックステートメント

制御フローステートメントには、その他の特性があります。それらには独自のスコープがあります。これは、次の実例では、スコープ外ではxにアクセスできないことを意味します。

while false:
  var x = "hi"
echo x # できない

while(for)ステートメントは、暗黙のブロックを導入します。識別子は、それらが宣言されているブロック内でのみ表示されます。blockステートメントを使用して、新しいブロックを明示的に開くことができます。

block myblock:
  var x = "hi"
echo x # できない

ブロックのlabel(この実例ではmyblock)はオプションです。

Breakステートメント

ブロックは、breakステートメントでジャンプアウトできます。breakステートメントは、whilefor、またはblockステートメントからジャンプできます。ブロックラベルが指定されていない限り、最も内側の構造からジャンプします。

block myblock:
  echo "entering block"
  while true:
    echo "looping"
break # ループから飛び出すが、ブロックからは飛び出せない
  echo "still in block"

block myblock2:
  echo "entering block"
  while true:
    echo "looping"
break myblock2 # ブロックから飛び出す(ループする)
  echo "still in block"

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と評価される最初の条件に対してのみコードを生成します。

whenステートメントは、C言語の#ifdef構造体と同様に、プラットフォーム固有のコードを記述するときに役立ちます。

ステートメントとインデント

基本的な制御フローステートメントについて説明したため、Nimインデントルールに戻りましょう。

Nimの単純な文と複雑な文には違いがあります。 単純なステートメントには、その他のステートメント(単純なステートメントに属する割り当て、プロシージャー呼び出し、またはreturnステートメント)を含めることはできません。ifwhenforwhileなどの複雑なステートメントには、その他のステートメントを含めることができます。あいまいさを避けるために、複雑なステートメントはインデントする必要がありますが、単一の単純なステートメントは必要がありません。

# 単一の代入ステートメントをインデントする必要はありません:
if x: x = false

# if文をネストするにはインデントする必要があります:
if x:
  if y:
    y = false
  else:
    y = true

# 条件の後に2つのステートメントがあるため、インデントする必要があります:
if x:
  x = false
  y = false

式は通常、値を持つステートメントの一部です。例えば、if文の条件は式です。読みやすさを向上させるために、式を特定の場所でインデントすることができます。

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などの新しいコマンドを定義するには、procedureの概念が必要です。(一部の言語はメソッドまたは関数と呼ばれます。)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」(または同様の答え)と答えるとtrueを返し、「no」(または同様の答え)と答えるとfalseを返すyesというプロセスを示しています。returnステートメントはすぐにプロセスから飛び出します。(question: string):bool文法がプロセスを記述するには、questionという名前のstring型の変数が必要であり、bool値を返します。ブール型が組み込まれています:有効な値はtruefalseのみです。ifまたはwhile文の条件はbool型である必要があります。

いくつかの用語:この実例では、questionは(フォーム)パラメータと呼ばれ、「Should I…」は実際のパラメータと呼ばれます。このパラメータに渡されます。

Result変数

値を返すプロシージャには、返す値を表す暗黙的なresult変数宣言があります。式のないreturnステートメントは、return resultの省略形です。終了時にreturnステートメントがない場合、resultは常にプロシージャの最後に自動的に返されます。

proc sumTillNegative(x: varargs[int]): int =
  for i in x:
    if i < 0:
      return
    result = result + i

echo sumTillNegative() # echos 0
echo sumTillNegative(3, 4, 5) # echos 12
echo sumTillNegative(3, 4 , -1 , 6) # echos 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
divmod(8, 5, x, y) # xとyを変更する
echo x
echo y

この実例では、resremaindervar parametersです。Varパラメータはプロシージャによって変更でき、変更は呼び出し元に表示されます。上記の実例では、varパラメータの代わりにタプルを返す値の型として使用する方が適切であることに注意してください。

Discardステートメント

副作用についてのみ値を返し、返す値を無視するプロシージャを呼び出すには、discardステートメントを使用する必要があります。 Nimでは、返す値を暗黙的に破棄することはできません。

discard yes("May I ask a pointless question?")

呼び出されたメソッドとイテレータがdiscardable pragmaで宣言されている場合、返す値の型は暗黙的に無視できます。

proc p(x, y: int): int {.discardable.} =
  return x + y

p(3, 4) # now valid

Commentsセクションで説明されている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を呼び出すため、実パラメータの順序は重要ではありません。順序付きパラメータと名前付きパラメータを混在させることに問題はありませんが、読むのは非常に簡単ではありません。

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)   # calls the toString(x: int) proc
echo toString(true) # calls the toString(x: bool) proc

toStringは通常、Nimの$であることに注意してください。)コンパイラはtoStringの呼び出しに最も適切なプロシージャを選択します。オーバーロード解決アルゴリズムについては、ここでは説明しません(マニュアルで指定されます)。それは事故を引き起こさず、非常に単純な統一されたアルゴリズムに基づいています。あいまいな呼び出しはエラーとして報告されます。

オペレーター

Nimライブラリはオーバーロードを多用します。1つの理由は、+のような各演算子がオーバーロードプロセスであるためです。パーサーを使用すると、インフィックストークン(a + b)またはプレフィックストークン(+ a)で演算子を使用できます。中置演算子には常に2つの実パラメータがあり、接頭演算子には常に1つの実パラメータがあります。(接尾辞演算子はあいまいであるため存在しません:a @ @ b(a) @ (@b) か、 (a@) @ (b)か、どちらを意味しますか?通常、 (a) @ (@b) を意味します。Nimには接尾辞演算子がないためです。

andornotなどのいくつかの組み込みキーワード演算子を除いて、演算子は常に以下の記号で構成されます:+ - * \ / < > = @ $ ~ & % ! ? ^ . |

ユーザー定義の演算子を許可します。独自の@!?+~演算子を定義することができますが、そうすると読みやすさが低下します。

オペレーターの優先順位は、最初の文字によって決定されます。詳細はマニュアルに記載されています。

バッククォート”“”を使用して、新しい演算子を定義します。

proc `$` (x: myDataType): string = ...
# $演算子がmyDataTypeに対して有効になり、オーバーロード解決により、組み込み型に対して$が以前と同じように機能することが保証されます。

““”マークは、その他のプロセスと同様にオペレーターを呼び出すためにも使用できます。

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

前方宣言

各変数、プロシージャなどは、前方宣言を使用する必要があります。前方宣言は互いに再帰することはできません。

# 前方宣言:
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)

ここでoddevenに依存し、逆にも同様です。そのため、完全に定義される前に、コンパイラに導入する必要さえあります。前方宣言の構文は単純です。=とプロシージャ本体を直接無視します。assertは境界条件を追加するだけです。これについては、モジュールのセクションで説明します。

言語の後続のバージョンは、前方宣言の要件を弱化します。

この実例は、プロシージャ本体が式で構成され、その値が暗黙的に返されることも示しています。

イテレータ

簡単なカウントの実例をご覧ください。

echo "Counting to ten: "
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)

これは無理です。問題は、プロセスがreturnだけでなく、イテレータの後ろにreturnとcontinueがすでに完了しました。 このreturn and continueはyieldステートメントと呼ばれます。現在残っているのは、procキーワードをiteratorに置き換えることだけです。これが最初のイテレータです。

iterator countup(a, b: int): int =
  var res = a
  while res <= b:
    yield res
    inc(res)

イテレータはプロセスのように見えますが、いくつかの重要な違いがあります。

  • イテレータは、ループ内からのみ呼び出すことができます。
  • イテレータにreturnステートメントを含めることはできません(プロシージャにyieldステートメントを含めることはできません)。
  • イテレータの暗黙的なresult変数はありません。
  • イテレータは再帰をサポートしていません。
  • コンパイラはイテレータをインライン化できる必要があるため、イテレータを前方宣言することはできません。(この制限は、コンパイラの将来のバージョンでなくなります。)

closureイテレータを使用して、さまざまな制限のセットを取得することもできます。イテレータには独自の名前空間があるため、イテレータはプロシージャと同じ名前とパラメータを持つことができます。 通常、イテレータを同じ名前のprocでラップします。これらのイテレータは、strutilsモジュールのsplitのように、結果を累積し、シーケンスとして返します。

基本的の型

この章では、基本的な組み込み型とその操作の詳細について説明します。

ブール値

Nimのブール型はboolと呼ばれ、truefalseの2つの事前定義された値で構成されます。while、if、elif、およびwhenステートメントの条件は、ブール型である必要があります。

ブール型に演算子not, and, or, xor, <, <=, >, >=, !=, == を定義します。 andおよびor演算子は、短絡評価を実行します。例えば:

while p != nil and p.name != "xyz":
# p == nilの場合、p.nameは評価されない
  p = p.next

char型

char型はcharと呼ばれます。サイズは常に1バイトであるため、ほとんどのUTF-8文字を表すことはできませんが、マルチバイトUTF-8文字を構成する1バイトを表すことはできます。その理由は効率のためです。UTF-8はこのために設計されているため、ほとんどのユースケースでは、プログラムはUTF-8を正しく処理できます。 文字リテラルは一重引用符で囲まれています。

charは、==, <, <=, >, >=演算子と比較できます。$演算子は、charを文字列に変換します。 文字を整数と混在させることはできません。ordプロシージャを使用して、charの序数値を取得します。整数から文字への変換はchrプロシージャを使用します。

文字列

文字列変数を変更したり、文字列を追加したりすることができ、非常に効率的です。Nimの文字列には長さフィールドがあり、ゼロで終わります。文字列の長さは、組み込みのlenプロシージャを使用して取得できます。長さは、後続のゼロをカウントしません。末尾のゼロへのアクセスはエラーです。cstringへのコピー変換なしでNim文字列にのみ存在します。

文字列の割り当てにより、コピーが作成されます。演算子を使用して、文字列を連結し、addを文字列に追加できます。

文字列は辞書式順序で比較され、すべての比較演算子がサポートされています。変換により、すべての文字列はUTF-8でエンコードされますが、必須ではありません。例えば、バイナリファイルから文字列を読み取る場合、それらは単なるバイトのシーケンスです。インデックス演算子s[i]は、i番目のunicharではなく、sのi番目の文字を表します。

文字列変数は、空の文字列 ""で初期化されます。

整数型

Nimには、以下の組み込み整数型が含まれます。int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64

デフォルトの整数型はintです。整数リテラルは、タイププレフィックスを使用して、デフォルト以外の整数型を指定できます。

let
  x = 0     # xは ``int``
  y = 0'i8  # yは ``int8``
  z = 0'i64 # zは ``int64``
  u = 0'u   # uは ``uint``

ほとんどの整数は、メモリ内のオブジェクトをカウントするために使用されるため、intとpointerのサイズは同じです。

整数は一般演算子 + - * div mod < <= == != > >=をサポートします。and or xor not演算子をサポートし、ビット単位の演算を提供します。 左シフトにはshlを使用し、右シフトにはshrを使用します。シフト演算子の引数は、常に符号なし整数として扱われます。通常の乗算または除算は算術シフトを行うことができます。

符号なし演算では、オーバーフローやアンダーフローは発生しません。

式でさまざまな型の整数が使用されている場合、ロスレス自動型変換が実行されます。 歪んでいると、EOutOfRange例外がスローされます(コンパイル時にエラーを確認できない場合)。

浮動小数点

Nimには、次の組み込み浮動小数点型が含まれます。float float32 float64

デフォルトの浮動小数点型はfloatです。 現在の実装では、floatは64ビットです。

浮動小数点リテラルには、デフォルト以外の浮動小数点型を指定するための型接頭辞を付けることができます。

var
  x = 0.0      # xは ``float``
  y = 0.0'f32  # yは ``float32``
  z = 0.0'f64  # zは ``float64``

浮動小数点型は一般的な演算子+ - * / < <= == != > >= をサポートし、IEEE-754標準に従います。

式で異なる型が使用されている場合、自動型変換が実行されます。短い型は長い型に変換されます。整数型は自動的に浮動小数点型に変換されません。その逆も同様です。toIntおよびtoFloatプロシージャを使用して変換します。

型変換

デジタル型変換は、型を使用して実行されます。

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プロシージャを使用できます。これは、任意の型または複雑なループデータグラフにも使用できます。次の実例では、基本型の出力でも$ and 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」を書き込む

すべての比較演算子は列挙型を使用できます。

列挙の記号

列挙の記号は、あいまいさを避けるために制限できます:Direction.south

$演算子は任意の列挙値をその名前に変換でき、ordプロシージャはそれを基底の整数型に変換できます。

その他のプログラミング言語とのインターフェースを改善するために、列挙型に明示的な序数値を割り当てることができ、序数値は昇順である必要があります。

順序型

列挙型、整数、charbool(およびサブ範囲)は、順序型と呼ばれます。順序型には、いくつかの特別な操作があります。

OperationComment
ord(x)xを表す整数値を返します。
inc(x)xは1ずつ増加します。
inc(x, n)xnずつインクリメントします。nは整数です。
dec(x)xが1ずつ減少します。
dec(x, n)xnずつ減少します。 nは整数です。
succ(x)xの次の値を返します。
succ(x, n)xの後のn番目の値を返します。
pred(x)xの前の値を返します。
pred(x, n)xの前のn番目の値を返します。

inc、dec、suc、およびpred操作は、EOutOfRangeまたはEOverflow例外をスローすることによって失敗します。(コードのコンパイル時にランタイムチェックがオンになっている場合。)

サブレンジ

サブレンジは、整数型または列挙型の値の範囲です(基本型)。例えば:

type
  MySubrange = range[0..5]

MySubrangeは、0から5のみを含むint範囲です。MySubrange型の変数にその他の値を割り当てると、コンパイル時または実行時のエラーになります。基本型をサブレンジに割り当てることができ、その逆も可能です。

systemモジュールは、重要なNatural型のrange[0..high(int)] を定義します(highは最大値を返します)。その他のプログラミング言語は、符号なし整数の使用を提案する場合があります。これはいい方法ではありません。数値を負にすることはできないため、符号なし算術を使用したくないです。NimのNatural型は、このプログラミングエラーを回避するのに役立ちます。

集合型

集合は、数学セットの概念をシミュレートします。セットの基本型は、固定サイズの順序型のみです。

  • int8-int16
  • uint8/byte-uint16
  • char
  • enum

または同等の型です。 符号付き整数セットの基本型は、0 .. MaxSetElements-1の範囲内にあると定義されています。その中で、MaxSetElementsは2^16です。

その理由は、セットが高性能ビットベクトルとして実装されているためです。より大きな型でセットを宣言しようとすると、エラーが発生します。

var s: set[int64] # エラー:集合が大きすぎる

集合は、集合コンストラクタで作成できます。{}は空の集合です。 空の集合は、その他の特定の集合型と互換性があります。 コンストラクタを使用して、要素(および要素範囲)を含めることもできます。

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

集合でサポートされる演算子:

演算子説明
A + B和集合
A * B交差
A - B差集合
A == B等しい
A <= Bサブセット
A < B真のサブセット
e in A要素
e notin AAには要素eが含まれていません
contains(A, e)要素eが含まれています
card(A)Aの基数(セットAの要素数)
incl(A, elem)A = A + {elem}と同じです
excl(A, elem)A = A - {elem}と同じです

ビットフィールド

集合は、プロセスインジケータを定義するためによく使用されます。これは、必須または一緒の整数定数を定義するよりも明確で、型もより安全です。

列挙、収集、および強制変換は一緒に使用できます。

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}

次にはセットが列挙を2の指数に変換する方法を説明します。

Cで列挙型と集合を使用する場合は、distinct cintを使用します。

Cと通信するには、bitsize pragmaを参照してください。

配列

配列は固定長のコンテナです。配列内の要素は同じ型です。 配列インデックスタイプは、任意の順序型にすることができます。

配列は[]で構築できます:

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] マークは、xのi番目の要素にアクセスするために使用されます。配列アクセスは常にバインドチェックされます(コンパイルする時または実行する時)。 これらのチェックは、pragmas、またはコンパイラのコマンドラインスイッチによって--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では、さまざまなディメンションでさまざまなインデックス型を使用できるため、ネスト構文は少し異なります。上記の例に基づいて、レイヤーの数が別の列挙によってインデックス付けされた列挙配列として定義されている場合、次の行を追加して、レイヤーの数で細分化された型(LightTower)を追加できます。

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, ...more output..
# 型が一致しないため、次の行はコンパイルできません
#tower[north][east] = on
#tower[0][1] = on

組み込みのlenプロシージャが、配列の最初の次元の長さのみを返すことに注意してください。ネストされた性質をよりよく説明するためにLightTowerを定義する別の方法は、上記で定義されたLevelSetting型を無視し、代わりに最初のディメンション型として直接埋め込むことです。

type
  LightTower = array[1..10, array[north..west, BlinkLights]]

配列ではゼロから開始するのが通常で、ゼロから指定されたインデックスから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型でインデックス付けされます。len、low、high演算子もシーケンスで使用できます。x[i]タグを使用して、xのi番目の要素にアクセスできます。

シーケンスは、配列コンストラクタ[]配列間演算子@を使用して構築できます。シーケンスにスペースを割り当てる別の方法は、組み込みのnewSeqプロシージャを呼び出すことです。

シーケンスは、開いている配列パラメータに渡すことができます。

Example:

var
  x: seq[int] # 整数列の参照
x = @[1, 2, 3, 4, 5, 6] # @は配列をヒープに割り当てられたシーケンスに変換する

シーケンス変数は@[]で初期化されます。

forステートメントは、シーケンスで使用する場合、1つまたは2つの変数を使用できます。変数の形式を使用する場合、変数はシーケンスによって提供される値を保持します。forステートメントは、システムモジュール内のitems()イテレータの結果を反復処理します。ただし、2変数形式を使用する場合、最初の変数はインデックス位置を保持し、2番目の変数は値を保持します。ここのforステートメントは、systemモジュールのpairs()イテレータの結果を繰り返し処理します。例えば:

for value in @[3, 4, 5]:
  echo value
# --> 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から始まるintでインデックスが付けられます。len、low、およびhigh演算子は、オープン配列にも使用できます。インデックス型に関係なく、基本型と互換性のある任意の配列をオープン配列パラメータに渡すことができます。

var
  fruits:   seq[string]       # 文字列シーケンスは「@[]」で初期化される
  capitals: array[3, string]  # 固定サイズの文字列配列

capitals = ["New York", "London", "Berlin"]   # 配列「capitals」では、3つの要素のみを割り当てることができる
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"])

変換は、変形可能パラメータがプロシージャーヘッドの最後のパラメータである場合にのみ実行されます。このシナリオでは、型変換も実行できます。

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に渡される実際のパラメータに適用されます。 $は空の文字列コマンドに適用されることに注意してください。

スライス

スライス構文はサブ範囲のように見えますが、さまざまなシナリオで使用されます。 スライスは、2つの境界abを含む単なるスライス型のオブジェクトです。その自体はあまり役に立ちませんが、その他の集合型は、範囲を定義するためにスライスオブジェクトを受け入れる演算子を定義します。

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.'

上記の実例では、スライスを使用して文字列の一部を変更しています。スライス境界は、その型でサポートされている任意の値を保持できますが、受け入れられる値を定義するのはスライスオブジェクトを使用するプロセスです。

文字列、配列、シーケンスなどのインデックスを指定するさまざまな方法を理解するには、Nimがゼロベースのインデックスを使用することを覚えておく必要があります。

そのため、文字列bの長さは19であり、インデックスを指定する2つの異なる方法は次のとおりです。

"Slices are useless."
 |          |     |
 0         11    17   インデックスを使用する
^19        ^8    ^2   ^を使用する

その中で、b[0..^1]b[0..b.len-1]およびb[0..<b.len] と同等であり、^1が指定されたb.len-1の省略形を提供することと見なすことができます。

上記の実例では、文字列がピリオドで終わっているため、文字列の「useless」部分を取得して「useful」に置き換えます。

b[11..^2]は「useless」の一部であり、b[11..^2] = "useful" は “useless”を “useful”に置き換え、「Slices are useful」という結果を取得します。

ヒント:オプションのメソッドは、 b[^8..^2] = "useful"b[11..b.len-2] = "useful" 、またはas b[11..<b.len-1] = "useful"です。

オブジェクト

さまざまな値を名前付きの単一の構造にまとめてパックするデフォルトの型は、オブジェクト型です。 オブジェクトは値型です。つまり、オブジェクトが新しい変数に割り当てられると、そのすべてのコンポーネントもコピーされます。

各オブジェクト型Fooには、コンストラクタFoo(field: value, …)があり、すべてのフィールドを初期化できるます。指定されていないフィールドはデフォルト値を取得します。

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

タプル

タプルは、これまでに見たオブジェクトと非常によく似ています。これらは、割り当て時に各コンポーネントをコピーする値型です。オブジェクト型とは異なり、タプル型は構造化型です。つまり、同じ型と同じ名前のフィールドを同じ順序で指定する場合、異なるタプル型は同等です。

コンストラクタ()は、タプルを構築するために使用できます。コンストラクタのフィールドの順序は、タプル定義の順序と一致する必要があります。ただし、オブジェクトとは異なり、ここではタプル型の名前を使用しないかもしれません。

オブジェクト型など、t.fieldはタプルのフィールドにアクセスするために使用されます。オブジェクトで使用できないもう1つの表記は、 i‘ 番目のフィールドにアクセスするためのt[i]です。ここで、iは定数整数でなければなりません。

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

# 次の行は異なるタプルであるため、コンパイルできない。
#person = building
# --> Error: type mismatch: got (tuple[street: string, number: int])
#     but expected 'Person'

タプルの型を宣言する必要がなくても、それを使用できます。異なるフィールド名で作成されたタプルは、同じフィールド型であっても、異なるオブジェクトと見なされます。

タプルは、変数の割り当て中にのみ解凍できます。これにより、タプルフィールドを名前付き変数に1つずつ直接割り当てることが容易になります。実例には、osモジュールモジュールのsplitFileプロシージャがあります。これは、パスのディレクトリ、名前、および拡張子を同時に返します。タプルのアンパックでは、割り当てるアンパックされた変数を括弧で囲む必要があります。そうしないと、各変数に同じ値が割り当てられます。例えば:

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

タプルフィールドは常にパブリックであり、オブジェクトタイプフィールドのようにエクスポートするために明示的にマークする必要はありません。

参照型とポインター型

参照(その他のプログラミング言語のポインターと同様です)は、多対一の関係を導入する方法です。これは、異なる参照が同じメモリ位置を指し、変更できることを意味します。

Nimは、追跡された引用と追跡されていない引用を区別します。追跡されていない参照は、ポインタとも呼ばれます。追跡された参照は、ガベージ収集されたヒープ内のオブジェクトを指し、追跡されていない参照は、手動で割り当てられたオブジェクトまたはメモリ内の他の場所のオブジェクトを指します。そのため、参照を追跡しないことは安全ではありません。一部の低レベルの操作(ハードウェアへのアクセスなど)では、追跡されていない参照が必要です。

追跡された参照はrefキーワードで宣言され、追跡されていない参照はptrキーワードで宣言されます。

空の[]添え字マークを使用して、参照を逆参照できます。これは、参照が指すコンテンツを取得することを意味します。.(タプル/オブジェクトフィールド演算子にアクセス)および[](配列/文字列/シーケンスインデックス演算子)演算子は、参照型の暗黙的な逆参照操作を実行します。

type
  Node = ref object
    le, ri: Node
    data: int
var
  n: Node
new(n)
n.data = 9
# n[].dataを書き込む必要はない。実際、n[].dataは勧めしない。

新しく追跡されたオブジェクトを割り当てるには、組み込みのプロシージャnewを使用する必要があります。追跡されていないメモリを処理するには、allocdealloc、およびreallocを使用できます。システムモジュールのドキュメントには、詳細が含まれています。

参照がnothingを指している場合、その値はnilです。

プロセス型

プロセス型は、プロシージャへのポインタです。 nilは、プロセス型変数に許可される値です。Nimは、関数型プログラミング手法を実現するためにプロセス型を使用します。

Example:

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に記載されています。

Distinct型

Distinct型は、「非基本型のサブ型」を作成するために使用できます。Distinct型のすべての動作を明示的に定義する必要があります。これを支援するために、Distinct型とその基本型を相互に強制することができます。実例はmanualに記載されています。

モジュール

Nimは、モジュールの概念を使用してプログラムをフラグメントに分割することをサポートしています。 各モジュールは独自のファイルにあります。このモジュールは、情報の隠蔽とコンパイルの分離を実現します。モジュールは、importステートメントを介して別のモジュールシンボルにアクセスできます。アスタリスク(*)でマークされた最上位のシンボルのみがエクスポートされます。

# Module A
var
  x*, y: int

proc `*` *(a, b: seq[int]): seq[int] =
  # 新しいシーケンスを割り当てる:
  newSeq(result, len(a))
  # 2つのシーケンスを乗算する:
  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])

上記のモジュールはx*をエクスポートしますが、yはエクスポートしません。

モジュールの最上位ステートメントは、プログラムの開始時に実行されます。例えば、これを使用して、複雑なデータ構造を初期化できます。

各モジュールには、メインファイルとしてコンパイルされたときにtrueとなる特別なマジック定数isMainModuleがあります。 上に示したように、これはモジュール内の組み込みテストに非常に役立ちます。

モジュールのシンボルは、module.symbol構文で修飾できます。記号があいまいな場合は、修飾する必要があります。あいまいなシンボルは、2つ以上の異なるモジュールで定義され、3番目のモジュールによってインポートされます。

# Module A
var x*: string
# Module B
var x*: int
# Module C
import A, B
write(stdout, x) # error: xがあいまいである
write(stdout, A.x) # okay: 制限を使用した

var x = 4
write(stdout, x) # あいまいさなし:モジュールCの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))   # あいまいさ:どの `x`を呼び出すか?

except識別子

通常のimportステートメントは、エクスポートされたすべてのシンボルをもたらします。これは、except識別子を使用して、除外するシンボルを制限できます。

import mymodule except y

Fromステートメント

単純な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

m.x()         # mはmymoduleの別称である

Includeステートメント

includeステートメントとモジュールのインポートは異なる基本的なタスクを実行します。ファイルの内容のみが含まれます。 includeステートメントは、大きなモジュールを複数のファイルに分割する場合に役立ちます。

include fileA, fileB, fileC

Share

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です