Help us understand the problem. What is going on with this article?

Nim basics を日本語訳してみた

More than 1 year has passed since last update.

Nimの公式ドキュメントにある Nim Baics の日本語訳が見つからなかったので翻訳してみました。

個人的に面白いなーと感じた点は
* 変数名のキャメルケースとスネークケースを区別しないこと
* 数値の桁区切りができること (10_000 == 10000)
* variable.proc() がメソッドの呼び出しではなく proc(varible) と同じだということ
あたりでしょうか。

それでは、どうぞ。


Nimの基礎

Nimは、比較的新しいプログラミング言語で、読みやすく高性能なコードが書けます。ですが、Nimのチュートリアルを読んでいれば、おそらくNimについて既にご存知でしょう。

チュートリアルは オンライン もしくは PDF よりご覧いただけます。

なお、このドキュメントは未完成です。エラーが見つかったりこのドキュメントに改善点があれば、issue tracker までご連絡ください。

対象となる読者

  • プログラミング経験がない、もしくは、乏しい方。
  • 他のプログラミング言語の経験がある方。
  • 1からNimを詳しく知りたい方。

対象とならない読者

  • プログラミング経験豊富な方。より高度なチュートリアルの方が適しているでしょう。チュートリアル または Nim by Example をご覧ください。
  • Nimの経験がある方。(ドキュメントの改善にご協力ください)

本チュートリアルの使い方

本チュートリアルの目的は、プログラミングの基本およびNimの構文をお伝えすることです。そのため他のチュートリアルより簡単で、より深く追求できるようになります。

読むだけではなく、実行したり、例文を改造したり、自分で例文を考えたり、全体的に興味をもつとベストです。各章の終わりの演習問題は飛ばしてはいけません。交渉の余地なしです。

本チュートリアルの内容以上のことを知りたい場合、NimフォーラムNim Gitter channelDiscord Server、NimのフリーノードIRCチャンネル(#nim)で質問してみてください。

インストール

Nimのインストール

Nimは主要3オペレーティングシステム向けにパッケージを提供しており、インストールに関しては複数のオプションがあります。

公式インストール手順に従って最新の安定版をインストールできます。また、最新の機能やバグ修正に興味がありましたら、最新の開発バージョンと安定版を簡単に切り替えられるchoosenimというツールを使うこともできます。

どちらにせよ、リンク先のインストール手順に従ってNimをインストールしてください。以下の章で、インストールがうまくいったか確認します。

Linuxをお使いであれば、高確率でディストリビューションのパッケージ・マネージャにNimが入っているはずです。この方法でインストールを行った場合は、最新バージョン(webサイトから確認してください)かどうか確認してください。それ以外の方法でインストールする場合は、上述の手順に従ってください。

本チュートリアルでは、現時点の安定版 Nim 0.19を使用します。

Additional Toolsのインストール

任意のテキストエディタでNimのコードを記述し、ターミナルからコンパイルして実行できます。シンタックスハイライトや補完機能をお望みであれば、人気のエディタにはプラグインがあり、これらの機能が提供されています。

シンタックスハイライトと補完が使えるNim Extensionと、すぐにコンパイルと実行ができるCode Runner Extensionを入れたVS Codeが筆者のお気に入りです。

他のエディターをお使いであれば、エディタのサポートについてWikiをご覧ください。

インストールのテスト

インストールが成功したかどうか確認するため、入門の例題としてお決まりのHello Worldを書いてみます。Nimでは、Hello World! の出力(プリンタで印刷するのではなく、画面上に表示)は簡潔で、おまじないも不要です。

helloworld.nim 等の新規ファイルに、下記の一行を書くだけです。

helloworld.nim
echo "Hello World!"

※出力したい文言は、echoコマンドの後ろに記述し、二重引用符(")で囲む必要があります。

まず、プログラムをコンパイルし、実行して期待通りの結果が得られるか見てみます。

ファイルがあるディレクトリ(Linuxの場合、ファイルマネージャを開いてディレクトリを右クリックし、「端末を開く」。Windowsの場合は、Shift + 右クリックして「コマンド ウィンドをここで開く」)

コンパイルするにはターミナル上で以下を入力します。

nim c helloworld.nim

コンパイルが終了したら、プログラムが実行できるようになります。Linuxではターミナルから./helloworldと入力し、Windowsではhelloworld.exeと入力すれば実行できます。

1コマンドでコンパイルと実行を両方実行できます。以下を入力します。

nim c -r helloworld.nim

cオプションでコンパイル、-rオプションで即時実行。すべてのオプションを表示するにはターミナル上でnim --helpと入力します。

前述のCode Runnerを入れたVS Codeをご利用の場合は、Ctrl+Alt+Nを入力すればコンパイルと実行が行われます。

いずれの方法で実行しても、すぐにウィンドウ(またはターミナル)に下記が表示されます。

Hello World!

おめでとうございます! はじめてののNimプログラムが正常に動作しました。

さて、これで画面上に何らかの文字を(echoコマンドを使用して)表示して、(ターミナル上でnim c programName.nimを入力して)コンパイルして、(様々な可能性のある)プログラムを実行する手段を知りました。

単純なNimプログラムを書くのに役立つ基本的な要素を追求し始めることができるようになりました。

命名の価値

プログラムの名前を大事にすると物事を追うのに役立ちます。ユーザに名前を尋ねるとして、後で使用するために保存しておきたいのです。それも、処理の度に何度も何度も尋ねないで、です。

pi = 3.14という例では、名前piは値3.14と結びついています。経験上、変数piは(10進)数だと言えます。

firstName = Aliceという例では、firstNameAliceという値を持つ変数名です。この変数の型は語だと言えるでしょう。

プログラミング言語でも同様です。これらの代入は、名前を持ちます。

変数宣言

Nimは静的型付けプログラミング言語です。つまり、値を使用する前に代入の型を宣言する必要があります。

Nimでは、変更できる値とできない値を区別しますが、後ほど詳しく説明します。varキーワードを使用して変数(変更可能な代入)を宣言できます。下記構文を使用して名前と型を記述(値は後で追加できます)するだけです。

var <名前>: <型>

あらかじめ値がわかっていれば、変数宣言の直後に値を渡せます。

var <名前>: <型> = <値>

山かっこ(<>)は変更可能であることを示すために使用しています。そのため、<名前>は、山かっこで囲まれた文字通りの単語ではなく、任意の名前です。

Nimは型推論もあり、コンパイラは、型を明示的に指定しなくても、代入文の値から型を検出できます。次の章では、様々な変数の型を説明します。

以下のように、明示的な型を指定しなくても変数に代入できます。

var <名前> = <値>

この例は下記のようになります。

var a: int  [1]
var b = 7   [2]
  • [1] 変数aはint(整数)型で、値は明示的に指定されていません。
  • [2] 変数bの値は7です。型は自動的に整数として検出されます。

代入の際はプログラムにとって意味のある名前を選ぶことが重要です。単純にabcといった具合に命名するとすぐに混乱するでしょう。名前にスペースを使用することはできません。名前が二つに分割されるためです。選択した名前が二つ以上の単語で構成されている場合、通常キャメルケース camelCase で記述します。(最初の文字は小文字である必要があります)

しかし、Nimは大文字と小文字を区別しないため、helloWorldhello_worldは同じ意味になります。例外は、文字列の最初の文字が大文字と小文字で区別されている場合(例:Helloworldhelloworld)のみです。名前には、数字、他のUTF-8の文字、もしお望みならば絵文字を含めることができますが、自分や他の人も入力しなければならないことに注意してください。

変数ごとにvarと入力する代わりに、同じvarブロック内で複数の変数(必ずしも同じ型である必要はありません)を宣言できます。Nimでは、ブロックは同じインデント(最初の文字の前にある同じ数のスペース)を持つコードの一部であり、デフォルトのインデントレベルはスペース2つです。このようなブロックは、代入部分に限ったことではなく、Nimプログラムのいたるところにがあります。

var
  c = -11
  d = "Hello"
  e = '!'

Nimでは、インデントにTabを使用することはできません。Tabを任意の数のスペースに置き換えるようエディタを設定できます。VS Codeのデフォルト設定では、Tabから4つのスペースへの変換です。設定(Ctrl+,)から "editor.tabSize": 2を設定することでで簡単に上書きされます。

前述のとおり変数は変更可能です。つまり、値は(複数回)変更される可能性がありますが、型は宣言時と同じものでなくてはなりません。

var f = 7               [1]

f = -3                  [2]
f = 19
f = "Hello" # error     [3] [4]
  • [1] 変数fの初期値は7で、int型とみなされます。
  • [2] fの値はまず-3に変更され、次に19に変更されます。これらは両方とも整数で、元の値と同じです。
  • [3] Helloは数値ではないため、fの値を "Hello" に変更しようとするとエラーとなり、fの型は整数から文字列に変更されます。
  • [4] #エラーはコメントです。コード中のコメントは#の後に記述します。以降、行内の記述はすべて無視されます。

不変代入

varキーワードで宣言された変数とは別に、Nimにはさらに2種類の代入が存在します。それらの値は変更できません。1つはconstキーワードで宣言され、もう1つはletキーワードで宣言されます。

Const

constキーワードを使用して宣言された不変代入の値は、コンパイル時(プログラムの実行前)にわかっていなければなりません。

例えば、重力加速度を const g = 9.81、円周率を const pi = 3.14 と宣言すると、これらの値はあらかじめわかっているためプログラムの実行中に変わることはありません。

const g = 35
g = -27         # error [1]

var h = -5
const i = h + 7 # error [2]
  • [1] 定数の値は変更できません。
  • [2] 変数hはコンパイル時には評価されません(変数であり、値がプログラムの実行中に変わる可能性があります)。その結果、定数iの値はコンパイル時にはわからず、エラーになります。

プログラミング言語によっては、定数の名前を大文字で書くのが一般的な方法ですが、Nimの定数は他の変数と同じように記述します。

Let

letで宣言された不変代入はコンパイル時にわかっている必要はなく、値はプログラムの実行中いつでも設定できますが、一度設定すると変更できません。

let j = 35
j = -27 # error [1]

var k = -5
let l = k + 7   [2]
  • [1] 不変値は変更できません。
  • [2] 前述のconstの例とは違って、こちらは動作します。

実際はconstよりletのほうが頻繁に現れたり使用することになるでしょう。

いかなる場合でもvarは使用できますが、基本的にはletを使用すべきです。値を変更する場合のみ変数にvarを使用してください。

基本的なデータ型

整数

前章で見たように、整数とは小数部と小数点なしで記述された数値です。

例えば、32-174010_000_000はすべて整数です。大きな数を読みやすくするため、千単位の区切り記号に_を使用できます(10000000ではなく10_000_000と表記すれば、1千万のことだとわかりやすくなります)。

通常の算術演算子、加算(+)、減算(-)、乗算(*)、除算(/)、は期待通り動作します。最初の3つの演算は常に整数を生成しますが、2つの整数を除算する場合は、余りがでなくても、結果は常に浮動小数点数(小数点がある値)となります。

整数の除算(小数点切り捨て)はdiv演算子で実現できます。整数の除算による剰余(法)に興味があればmod演算子を使用します。この2つの演算結果は常に整数値となります。

integers.nim
let
  a = 11
  b = 4

echo "a + b = ", a + b      [1]
echo "a - b = ", a - b
echo "a * b = ", a * b
echo "a / b = ", a / b
echo "a div b = ", a div b
echo "a mod b = ", a mod b
  • [1] echoコマンドは、コンマ区切りの文字をすべて画面に出力します。ここでは、最初に文字列 a + b = を出力し、同じ行に式 a + b の結果を出力します。

上記をコンパイルして実行すると、次のように出力されます。

a + b = 15
a - b = 7
a * b = 44
a / b = 2.75
a div b = 2
a mod b = 3

小数

浮動小数点数は、実数の近似表現です。

例えば、2.73-3.145.04e7は浮動小数点数です。eの後が指数になるような大きな浮動小数点数には科学的表記法を使用できます。この例では、4e74 * 10 ^ 7を表します。

floats.nim
let
  c = 6.75
  d = 2.25

echo "c + d = ", c + d
echo "c - d = ", c - d
echo "c * d = ", c * d
echo "c / d = ", c / d
c + d = 9.0         [1]
c - d = 4.5
c * d = 15.1875
c / d = 3.0         [1]
  • [1] 加算と除算の例では、小数部分がなくなっても、結果はまだ浮動小数点型であることに注意してください。

算術演算の優先順位はご想像どおりで、乗算と除算は加算と減算よりも優先されます。

echo 2 + 3 * 4
echo 24 - 8 / 4
14
22.0

浮動小数点数と整数の変換

Nimでは、型が異なる変数の算術演算は行えません。エラーとなります。

let
  e = 5
  f = 23.456

echo e + f   # error

変数の値は同じ型に変換する必要があります。変換は用意で、整数への変換はint関数を、浮動小数点数への変換はfloat関数を使用します。

let
  e = 5
  f = 23.987

echo float(e)      [1]
echo int(f)        [2]

echo float(e) + f  [3]
echo e + int(f)    [4]
  • [1] 整数eを浮動小数点数にしたものを出力します。 (eは整数型のままです)
  • [2] 浮動小数点数fint型にしたものを出力します。
  • [3] どちらのオペランドも浮動小数点数であり、加算できます。
  • [4] どちらのオペランドも整数であり、加算できます。
5.0
23
28.987
28

int関数を使用して浮動小数点数を整数に変換すると丸められません。小数点以下が切り捨てられます。
丸めるには別の関数を呼び出す必要がありますが、そのためにはNimの使い方についてもう少し詳しく知っておく必要があります。

文字

char型はASCII文字一文字を表すために使用します。

文字は2つの単一引用符(')の間に記述します。使用できるのは、文字、記号、1桁の数字です。複数桁の数字や文字があるとエラーになります。

let
  h = 'z'
  i = '+'
  j = '2'
  k = '35' # error
  l = 'xy' # error

文字列

文字列は連続した文字の表現に使用します。2つの二重引用符 (") の間に記述します。

文字列を単語と考えることもできますが、複数の単語、記号、数字を含めることができます。

strings.nim
let
  m = "word"
  n = "A sentence with interpunction."
  o = ""    [1]
  p = "32"  [2]
  q = "!"   [3] 
  • [1] 空文字
  • 2 数値ではありません。二重引用符に囲まれており、文字列となります。
  • [3] 一文字ですが、二重引用符に囲まれているため単一の文字ではありません。

特殊な文字

下記文字列を出力しようとすると、

echo "some\nim\tips"

こうなって驚くかもしれません。

some
im  ips

これは、特殊な意味を持つ文字があるためです。文字の前にエスケープ文字 \ をつけて使用します。

  • \n は改行文字です。
  • \t はタブ文字です。
  • \\ はバックスラッシュです(\ がエスケープ文字として使用されるため)。

上記の例を書いたとおりに出力したい場合、2通りあります。

  • バックスラッシュを出力するには、\ の代わりに \\ を使用します。
  • 構文 r"…​"(最初の引用符の直前に文字 r を入れる)を使用して文字列をそのまま入力します。この構文内では、エスケープ文字に特別な意味はありません。すべてそのまま出力されます。
echo "some\\nim\\tips"
echo r"some\nim\tips"
some\nim\tips
some\nim\tips

特殊文字は上記に限りません。なお、Nimのマニュアル内にすべて記述されています。

文字列の連結

Nimの文字列は可変です。つまり、内容が変わる可能性があります。add関数を使うと、既存の文字列に別の文字列か単一の文字を追加できます。元の文字列を変更したくない場合は、& 演算子を使用して文字列を連結(結合)することもできます。これにより、新しい文字列が返されます。

stringConcat.nim
var                     [1]
  p = "abc"
  q = "xy"
  r = 'z'

p.add("def")            [2]
echo "p is now: ", p

q.add(r)                [3]
echo "q is now: ", q

echo "concat: ", p & q  [4]

echo "p is still: ", p
echo "q is still: ", q
  • [1] 文字列を変更するつもりであれば、var で宣言しなければなりません。
  • [2] 別の文字列を追加すると、既存の文字列 p がその場で変更されます。
  • [3] 文字列に単一の文字を追加することもできます。
  • [4] 2つの文字列を連結すると、元の文字列を変更せずに新しい文字列が生成されます。
p is now: abcdef
q is now: xyz
concat: abcdefxyz
p is still: abcdef
q is still: xyz

ブーリアン

ブーリアン(または単にブール)型は、truefalse の2つの値しかありません。ブーリアンは通常、関係演算子の結果で、制御構文(次章)に使用されます。

ブーリアン変数の命名規則は、通常、単純なイエス・ノー(true・false)で答えられるものです。例えば、isEmptyisFinishedisMovingなどです。

関係演算子

関係演算子は、比較可能なエンティティの関係を評価します。

2つの値が同じかどうかを比較するには、==(等号2つ)を使用します。= と混同しないでください。こちらは、前述したように代入に使用します。

整数に関わる、定義済み関係演算子は以下のとおりです。

relationalOperators.nim
let
  g = 31
  h = 99

echo "g は h より大きい: ", g > h
echo "g は h より小さい: ", g < h
echo "g は h と等しい: ", g == h
echo "g は h と等しくない: ", g != h
echo "g は h 以上: ", g >= h
echo "g は h 以下: ", g <= h
g は h より大きい: false
g は h より小さい: true
g は h と等しい: false
g は h と等しくない: true
g は h 以上: false
g は h 以下: true

単一の文字や文字列を比較することもできます。

relationalOperators.nim
let
  i = 'a'
  j = 'd'
  k = 'Z'

echo i < j
echo i < k  [1]

let
  m = "axyb"
  n = "axyz"
  o = "ba"
  p = "ba "

echo m < n  [2]
echo n < o  [3]
echo o < p  [4]
  • [1] 大文字は小文字の前に来ます。
  • [2] 文字列比較は文字ごとに行われます。最初の3文字は同じで、文字bは文字zよりも小さいです。
  • [3] 同一かどうかの比較でなければ、文字列の長さは関係ありません。
  • [4] 短い文字列は長い文字列よりも小さくなります。
true
false
true
true
true

論理演算子

論理演算子は、1つ以上のブーリアン値から構成される式の真偽を評価するのに使用します。

  • 論理積 (and) は、両方のメンバが true の場合にのみ true を返します。
  • 論理和 (or) は、メンバのうち少なくとも1つが true の場合に true を返します。
  • 排他的論理和 (xor) は、片方が true で、もう片方が true でない場合に true を返します。
  • 論理否定 (not) は、メンバの真偽値を反転します。truefalse へ、そして逆も同様です(被演算子を1つしか取らない唯一の論理演算子です)。
logicalOperators.nim
echo "T and T: ", true and true
echo "T and F: ", true and false
echo "F and F: ", false and false
echo "---"
echo "T or T: ", true or true
echo "T or F: ", true or false
echo "F or F: ", false or false
echo "---"
echo "T xor T: ", true xor true
echo "T xor F: ", true xor false
echo "F xor F: ", false xor false
echo "---"
echo "not T: ", not true
echo "not F: ", not false
T and T: true
T and F: false
F and F: false
---
T or T: true
T or F: true
F or F: false
---
T xor T: false
T xor F: true
F xor F: false
---
not T: false
not F: true

関係演算子と論理演算子を組み合わせて、より複雑な式をつくることができます。

例えば、(5 < 7) and (11 + 9 == 32 - 2*6)true and (20 == 20) になり、さらに true and true になり、最終的に true が得られます。

まとめ

本章はこのチュートリアルで最も長い章で、多岐にわたりました。時間をとって各データ型を調べ、それぞれのデータ型で何ができるか試してみてください。

型は最初は拘束されたかのように思えるかもしれません。しかし、Nimコンパイラによるコードの高速化と、意図しない不具合を起こさないようにできます。

さて、これで基本的なデータ型と演算方法がわかりました。Nimで簡単な処理をするのに十分なはずです。以下の演習をしてテストしてみてください。

演習

  1. あなたの年齢を設定した不変変数を作成してください。作成したら年齢を日数で出力してください。(1年365日)
  2. あなたの年齢が3で割り切れるかどうか確認してください。(ヒント:modを使ってください)
  3. あなたの身長をセンチメートルで設定した不変変数を作成してください。作成したら身長をインチで出力してください。(1インチ = 2.54 cm)
  4. パイプの直径は3/8インチです。直径をセンチメートルで表現してください。
  5. あなたの姓を設定した不変変数と、名を設定した別の変数を作成してください。前の2つの変数を連結して、変数 fullName を作成してください。間に空白を入れるのを忘れないでください。そしてフルネームを出力してください。
  6. アリスは15日ごとに400ドルを稼ぎます。ボブは1時間あたり3.14ドルを稼ぎ、1日8時間、週7日働いています。30日後、アリスの収入はボブ以上になりますか? (ヒント:関係演算子を使う)

制御構文

これまでのプログラムはコードの全行が実行されていました。制御構造文を使用すると、何らかの条件が満たされた場合に、一部のコードのみ実行できるようになります。

プログラムを道路と考えれば、制御構造は分岐として考えることができ、状況に応じて進路を選択します。例えば、ある値段よりも安ければ卵を購入する、あるいは、雨が降っていれば傘を持っていき、それ以外の場合はサングラスを持っていく、などです。

擬似コードで書くと、上記2つの例は以下のようになります。

if eggPrice < wantedPrice:
  buyEggs

if isRaining:
  bring umbrella
else:
  bring sunglasses

下記のようにNimの構文と似ています。

if文

上記のif文は、プログラムを分岐させる最も簡単な方法です。

if文を書くためのNimの構文は、下記のとおりです。

if <条件>:                  [1]
  <インデントされたブロック>    [2]
  • [1] 条件はブーリアン型である必要があります。ブール変数、もしくは、関係式か論理式あるいはその複合文です。
  • [2] ifの次の行からは、2つのスペースでインデントするとブロックを生成し、条件が true の場合にのみ実行されます。

if文は入れ子にすることができます。つまり、1つのifブロック内に別のif文を入れることができます。

if.nim
let
  a = 11
  b = 22
  c = 999

if a < b:
  echo "a は b より小さい"
  if 10*a < b:                    [1]
    echo "a は b より更に小さい"

if b < c:
  echo "b は c より小さい"
  if 10*b < c:                    [2]
    echo "b は c より更に小さい"

if a+b > c:                       [3]
  echo "a 足す b は c より大きい"
  if 1 < 100 and 321 > 123:       [4]
    echo "1が100より小さいことを知ってましたか?"
    echo "それから、なんと、321は123より大きいんです!"
  • [1] 最初の条件は真、2番目の条件は偽になります。内部の echo は実行されません。
  • [2] どちらの条件も当てはまり、どちらの行も出力されます。
  • [3] 最初の条件は偽です。ブロック内のすべての行はスキップされ、何も出力されません。
  • [4] if文の中で論理和を使用しています。
a は b より小さい
b は c より小さい
b は c より更に小さい

Else

Elseはifブロックの後に続き、if文の条件が真ではないときに実行されるコードへ分岐します。

else.nim
let
  d = 63
  e = 2.718

if d < 10:
  echo "d は小さい数"
else:
  echo "d は大きい数"

if e < 10:
  echo "e は小さい数"
else:
  echo "e は大きい数"
d は大きい数
e は小さい数

条件が false の場合にのみ実行したい場合は、not演算子を使用して条件を反転できます。

Elif

Elifは "else if"の省略で、複数のif文をつなげることができます。

真と判断するまですべての条件文を評価します。見つかれば、それ以降はすべて無視します。

let
  f = 3456
  g = 7

if f < 10:
  echo "f は10より小さい"
elif f < 100:
  echo "f は 1から100の間"
elif f < 1000:
  echo "f は 100から1000"
else:
  echo "f は1000より大きい"

if g < 1000:
  echo "g は 1000より小さい"
elif g < 100:
  echo "g は 100より小さい"
elif g < 10:
  echo "g は 10より小さい"
f は1000より大きい
g は 1000より小さい

gの場合、3つの条件をすべて満たしていても、最初の分岐だけが実行され、他のすべての分岐は自動的にスキップされます。

Case

case文は、複数のelifを含むif文と同様に、複数の分岐のうち1つを選択するための別の方法です。ただし、case文は複数の条件を取ることができませんが、任意の固定値と、対応するパスを取ります。

if-elifで書くと

if x == 5:
  echo "Five!"
elif x == 7:
  echo "Seven!"
elif x == 10:
  echo "Ten!"
else:
  echo "unknown number"

case文で書くと

case x
of 5:
  echo "Five!"
of 7:
  echo "Seven!"
of 10:
  echo "Ten!"
else:
  echo "unknown number"

if文とは違い、case文は取りうるすべてのケースをカバーしなければなりません。そうしたくなければ else: discard を使うことができます。

case.nim
let h = 'y'

case h
of 'x':
  echo "x を選びました"
of 'y':
  echo "y を選びました"
of 'z':
  echo "z を選びました"
else: discard  [1]
  • [1] hの3つの値だけに関心があっても、他のすべての可能性のあるケース(他のすべての文字)をカバーするためにこの行を含める必要があります。含めなければコンパイルされません。
You've chosen y

同じ処理が複数の値に対して発生する場合は、各ケースに複数の値を使用することもできます。

multipleCase.nim
let i = 7

case i
  of 0:
    echo "i はゼロ"
  of 1, 3, 5, 7, 9:
    echo "i は奇数"
  of 2, 4, 6, 8:
    echo "i は偶数"
  else:
    echo "i が大きすぎます"
i は奇数

ループ

ループはもう一つの制御構造で、任意のコードを複数回実行できます。繰り返しの回数が決まっているもの(forループ)または、何らかの条件が満たされている限り繰り返す(whileループ)のいずれかです。

forループ

for文の構文は

for <ループ変数> in <ループ可能>:
  <ループ内処理>

伝統的にループ変数の名前に i が使われますが、別の名前も使用できます。ループ変数はループの内側でのみ利用可能です。ループが終了すると変数の値は破棄されます。

ループ可能は、反復可能なオブジェクトです。前述したとおり、文字列は反復可能なオブジェクトです。 (反復可能な型は次章で紹介します。)

ループ内のすべてのループ内処理はループごとに実行されるため、コードの繰り返し部分を効率的に記述できます。

Nimでは(整数の)数の範囲を反復したい場合、反復可能 オブジェクトの構文は start .. finish です。ここで、startfinish は数値です。これは、startfinish の両方を含む、start から finish まですべての数を繰り返します。デフォルトの反復可能の範囲は、startfinish よりも小さい必要があります。

ある数(これを含まないで)まで繰り返したい場合は、..< を使用できます。

for1.nim
for n in 5 .. 9:  [1]
  echo n

echo ""

for n in 5 ..< 9: [2]
  echo n
  • [1] ..を使用して繰り返す場合、両端は範囲に含まれます。
  • [2] 同じ範囲でも ..< を使用してを繰り返す場合、上限を除いた中で最後の値まで繰り返されます。
5
6
7
8
9

5
6
7
8

1以外のステップで繰り返したい場合は、countupを使用します。countup では、開始値、終了値(範囲に含む)、ステップサイズを定義します。

for2.nim
for n in countup(0, 16, 4):  [1]
  echo n
  • [1] 0 から 16 まで 4 ずつカウントアップされます。終端 (16) は範囲に含まれます。
0
4
8
12
16

startfinish よりも大きい範囲を繰り返すには、countdownという同様の関数が使用できます。カウントダウンしても、ステップサイズは正数になります。

for2.nim
for n in countdown(4, 0):       [1]
  echo n

echo ""

for n in countdown(-3, -9, 2):  [2]
  echo n
  • [1] 大きい数値から小さい数値へ繰り返すには、countdown を使用する必要があります(..演算子は、開始値が終了値よりも小さい場合にのみ使用できます)。
  • [2] カウントダウンするときでも、ステップサイズは正数でなければなりません。
4
3
2
1
0

-3
-5
-7
-9

文字列は繰り返し可能なため、forループを使用して各文字を処理できます(この種の繰り返しはfor-eachループとも言います)。

for3.nim
let word = "alphabet"

for letter in word:
  echo letter
a
l
p
h
a
b
e
t

ループカウンタ(ゼロ始まり)も必要ならば、for <カウンタ変数>, <ループ変数> in <イテレータ>:構文を使用します。繰り返しは反復可能オブジェクト1つで行い、別の反復可能オブジェクトを使用して同じ地点にアクセスしたい場合に便利です。

for3.nim
for i, letter in word:
  echo "letter ", i, " is: ", letter
letter 0 is: a
letter 1 is: l
letter 2 is: p
letter 3 is: h
letter 4 is: a
letter 5 is: b
letter 6 is: e
letter 7 is: t

whileループ

whileループはif文に似ていますが、条件が満たされている限りブロック内のコードを実行し続けます。繰り返しの回数が事前にわからない場合に使用します。

無限ループにならないよう、どこかの時点で終了する必要があります。

while.nim
var a = 1

while a*a < 10:     [1]
  echo "a is: ", a
  inc a             [2]

echo "final value of a: ", a
  • [1] この条件は、ループ内部の処理を実行する前に毎回チェックされます。
  • [2] inc は、a のインクリメントに使用します。 a = a + 1 または a + = 1 と同じです。
a is: 1
a is: 2
a is: 3
final value of a: 4

Break と Continue

break文は通常、何らかの条件が満たされた場合に繰り返しを途中で終了するために使用します。

次の例では、breakを含むif文がない場合、繰り返しが実行され続け、iが1000になるまで出力し続けます。break文を使用すると、iが3になると、繰り返しはすぐに(iの値を出力する前に)終了します。

break.nim
var i = 1

while i < 1000:
  if i == 3:
    break
  echo i
  inc i
1
2

continue文は、現在の反復位置の残りの行を実行せずに、すぐに次の反復を開始します。下記コードの出力から3と6が抜けていることに注意してください。

continue.nim
for i in 1 .. 8:
  if (i == 3) or (i == 6):
    continue
  echo i
1
2
4
5
7
8

演習

  1. コラッツの問題は、一般的な数学の問題でルールは単純です。まずに数を選びます。奇数なら3倍して1を足し、偶数なら2で割ります。これを1になるまで繰り返します。例えば、 5 → 奇数 → 3 * 5 + 1 = 16 → 偶数 → 16 / 2 = 8 → 偶数 → 4 → 2 → 1 → 終了!
    整数を(可変変数として)選び、コラッツの問題の各ステップを表示するループを作成してください。(ヒント:除算にdivを使う)

  2. あなたのフルネームをセットした不変変数を作成してください。forループを記述し、その文字列を反復処理して母音(a、e、i、o、u)だけを表示してください。(ヒント:それぞれで分岐させたcase文を使う)

  3. Fizz Buzzは子供のゲームで、基本的なプログラミングの知識を試すのによく使用されます。1から順に数を数えます。数字が3で割り切れればfizzで置き換え、5で割り切れればbuzzで置き換え、15(3と5の両方)で割り切れればfizzbuzzで置き換えます。少しお見せすると、1、2、Fizz、4、Buzz、Fizz、7、…のようになります。
    Fizz Buzzを30まで出力するプログラムを作成してください。(ヒント:除算できるか判定する際の順序に注意してください)

  4. 前の演習では、インチをセンチメートルに、またその逆に変換しました。今度はいくつかの値を記述した変換表を作成してください。例えば、下記のようになります。

in  | cm
----------------
1   | 2.54
4   | 10.16
7   | 17.78
10  | 25.4
13  | 33.02
16  | 40.64
19  | 48.26

コンテナ

コンテナは、項目のコレクションを含む、要素にアクセスできるデータ型です。通常、コンテナも反復可能です。つまり、ループの章で文字列を使用したのと同じ方法で使用できます。

たとえば、食料品リストは購入したい項目のコンテナであり、素数のリストは数値のコンテナです。

擬似コードでは以下のようになります。

groceryList = [ハム, 卵, パン, りんご]
primes = [1, 2, 3, 5, 7]

配列

配列は最も単純なコンテナ型です。配列は同質です。つまり、配列内のすべての要素は同じ型でなければなりません。配列も一定サイズであるため、要素数(または可能な要素数)はコンパイル時にわかっていなければなりません。これが、配列を「一定の長さをもつ同質コンテナ」と呼ぶ意味です。

配列型は array[<長さ>, <型>] を使って宣言します。ここで、長さは配列の最大長(格納する要素数)、そしてはすべての要素で共通の型です。要素から長さと型の両方を推測できる場合は、宣言を省略できます。

配列の要素は角括弧で囲みます。

var
  a: array[3, int] = [5, 7, 9]
  b = [5, 7, 9]        [1]
  c = []  # error      [2]
  d: array[7, string]  [3]
  x = 5


* [1] 値を設定すれば、配列bの長さと型はコンパイル時にわかります。省略しないのが正しい書き方ですが、配列aのように特別に宣言する必要はありません。

  • [2] この宣言では、要素の長さも型も推論することができず、エラーになります。

  • [3] 空の配列(値は別途設定)の宣言は、要素の値を設定せずに長さと型を指定するのが正しい方法です。配列dには7つの文字列を設定できます。

コンパイル時に配列の長さがわかっている必要があるので、下記は動作しません。

let n = 5

var a: array[n, char] # error [1]
  • [1] nletで宣言されているためエラーになります。値はコンパイル時にわかりません。長さのパラメータにはconstで宣言された値はしか使用できません。

シーケンス

シーケンスは配列に似たコンテナですが、長さはコンパイル時にわかっている必要はなく、実行時に変わる可能性があります。宣言するのはseq[<型>]へ入れる要素の型だけです。シーケンスも同質です。つまり、シーケンス内のすべての要素は同じ型でなければなりません。

シーケンスの要素は@[]で囲まれています。

var
  e1: seq[int] = @[]   [1]
  f = @["abc", "def"]  [2]
  • [1] 空のシーケンスを定義する必要があります。
  • [2] 空ではないシーケンスは推測可能です。この場合は文字列が格納されたシーケンスになります。

空のシーケンスを初期化するもう1つの方法は、newSeqプロシージャを呼び出すことです。次章でプロシージャの呼び出しについて見ていきますが、今は下記も可能であることを知っておくだけで十分です。

var
  e = newSeq[int]() [1]

* [1] 角括弧内に型パラメータを指定すると、その型のシーケンスを返します。よくあるエラーは末尾の () 忘れです。

文字列の場合と同様に、add関数を使用してシーケンスに新しい要素を追加できます。追加する際は、シーケンスは可変(varで定義)である必要があり、追加する要素はシーケンスの要素と同じ型である必要があります。

seq.nim
var                     [1]
  g = @['x', 'y']
  h = @['1', '2', '3']

g.add('z')              [2]
echo g

h.add(g)                [3]
echo h
  • [1] 可変の型
  • [2] 型が同じ新しい要素(char)を追加しています。
  • [3] 同じ型の別のシーケンスを追加しています。
@['x', 'y', 'z']
@['1', '2', '3', 'x', 'y', 'z']

既存のシーケンスに異なる型を渡そうとするとエラーになります。

var i = @[9, 8, 7]

i.add(9.81) # error   [1]
g.add(i)    # error   [2]
  • [1] intのシーケンスにfloatを追加しようとしています。
  • [2] intのシーケンスをcharのシーケンスに追加しようとしています。

シーケンスの長さはさまざまであるため、長さを取得する方法が必要です。len関数を使用します。

var i = @[9, 8, 7]
echo i.len

i.add(6)
echo i.len
3
4

インデックスとスライス

インデックスを使用して、コンテナから特定の要素を取得することができます。インデックスはコンテナ内の位置と考えてください。

Nimは、他の多くのプログラミング言語と同様、インデックスは0から始まります。つまり、コンテナの最初の要素のインデックスは0で、2番目の要素のインデックスは1になります。

「末尾から」要素を指定する場合は、接頭辞^を使用します。最後の要素(後ろから1番目)のインデックスは^1です。

添字を使用する際の構文は <コンテナ>[<インデックス>]です。

indexing.nim
let j = ['a', 'b', 'c', 'd', 'e']

echo j[1]   [1]
echo j[^1]  [2]
  • [1] インデックスは0始まりなので、インデックス1の要素は b です。
  • [2] 最後の要素を取得しています。
b
e

スライスすることで、1回の呼び出しで一連の要素を取得できます。これには範囲と同じ構文を使用します(forループの章で紹介)。

start .. stop構文の場合は、両端がスライスに含まれます。start ..< stop構文では、stopの要素はスライスに含まれません。

スライスの構文は <コンテナ>[<start> .. <stop>] です。

indexing.nim
echo j[0 .. 3]
echo j[0 ..< 3]
@[a, b, c, d]
@[a, b, c]

インデックスとスライスの両方を使用して、既存の可変コンテナや文字列に新しい値を割り当てることができます。

assign.nim
var
  k: array[5, int]
  l = @['p', 'w', 'r']
  m = "Tom and Jerry"

for i in 0 .. 4:  [1]
  k[i] = 7 * i
echo k

l[1] = 'q'        [2]
echo l

m[8 .. 9] = "Ba"  [3]
echo m
  • [1] 長さ5の配列の場合は、インデックスは0から4までです。配列の各要素に値を代入します。
  • [2] シーケンスの2番目の要素(インデックス1)を代入(変更)します
  • [3] インデックスが8から9の文字列の文字を変更します。
[0, 7, 14, 21, 28]
@['p', 'q', 'r']
Tom and Barry

タプル

これまでに見てきたコンテナはどちらも同質です。一方、タプルは異種データを含んでいます。つまり、タプルの要素にはさまざまな型の指定が可能です。配列と同様に、タプルは固定サイズです。

タプルの要素は括弧で囲みます。

tuples.nim
let n = ("Banana", 2, 'c')  [1]
echo n
  • [1] タプルにはさまざまなタイプの項目を含めることができます。この場合は、stringintcharです。
(Field0: "Banana", Field1: 2, Field2: 'c')

タプルの各フィールドには区別するために名前を付けることもできます。これは、インデックスを使用する代わりに、タプルの要素にアクセスするために使用できます。

tuples.nim
var o = (name: "バナナ", weight: 2, rating: 'c')

o[1] = 7          [1]
o.name = "りんご"  [2]
echo o
  • [1] フィールドのインデックスを使用してフィールドの値を変更する。
  • [2] フィールドの名前を使用してフィールドの値を変更する。
(name: "りんご", weight: 7, rating: 'c')

演習

  1. 整数を10個格納できる空の配列を作成してください。

    • その配列に10、20、…、100の数値を格納してください。(ヒント:ループを使用)
    • インデックスが奇数(値20、40、…)である要素だけを出力してください。
    • インデックスが偶数の要素に5を掛けてください。変更した配列を出力してください。
  2. コラッツの問題をやり直しますが、今回は各ステップを出力する代わりに、シーケンスに追加してください。

    • 始める数を選びます。特に興味深いのは、9、19、25、27です。
    • 始める数しか持たないシーケンスを作成してください。
    • 以前と同じロジックを使用して、1に達するまでシーケンスに要素を追加し続けてください。
    • シーケンスの長さとシーケンス自体を表示してください。
  3. コラッツの問題で、2から100までの範囲の中でシーケンスが最長となる数を見つけてください。

    • 各範囲内のそれぞれの数について、シーケンスを計算してください。
    • 現在のシーケンスの長さが前のレコードより長い場合は、現在の長さと数を新しいレコードに保存してください(タプル(longestLengthstartingNumber)または2つの別々の変数を使用)。
    • シーケンスが最も長かった数とその長さを出力してください。

プロシージャ

プロシージャ、他のプログラミング言語でいう関数は、特定のタスクを実行するコードの一部であり、ユニットとしてまとめられます。このようにコードをグループ化することの利点は、プロシージャのコードを使用したいときに、すべてのコードをもう一度書かなくても、プロシージャを呼び出すだけですむことです。

これまでの章で、さまざまなシナリオでコラッツの問題を扱いました。コラッツの問題のロジックをプロシージャにまとめることで、すべての演習で同じコードを呼び出すことができるようになります。

これまで、組み込みのプロシージャを使用してきました。出力を行うecho、シーケンスへの要素の追加を行うadd、整数の値を増やすinc、コンテナの長さを取得するlenなどです。独自のプロシージャを作成して使用する方法をお見せします。

プロシージャを使用する利点は下記のとおりです。

  • コードの重複を減らす
  • 何をする部分なのか名前を付けることができるのでコードが読みやすくする
  • 複雑なタスクをより簡単なステップに分解する

この章の冒頭で述べたように、プロシージャは他の言語では関数と呼ばれることがよくあります。関数の数学的定義を考えると、この名前は実際には少し不適切です。数学の関数は(f(x)のように)引数を取り、同じ入力に対して常に同じ答えを返します。

一方、プログラムにおけるプロシージャでは、特定の入力に対して常に同じ出力が返されるとは限りません。時には何も返さないことがあります。これは、先に述べた変数にプログラムが状態を格納し、プロシージャが読み込みと変更を行うことができるためです。Nimでは、funcという単語は現在、数学的な意味での関数として使用するために予約されており、副作用はありません。

プロシージャの宣言

プロシージャを使用する(呼ぶ)前に、作成し、何をするのか定義する必要があります。

プロシージャは、procキーワードとプロシージャ名を使用して宣言し、続いて引数とその型を括弧内で囲みます。最後の部分はコロンとプロシージャが返す値の型です。

proc <プロシージャ名>(<引数1>: <型1>, <引数2>: <型2>, ...): <戻り値の型>

プロシージャの本体は、宣言部に=記号を追加した後の、インデントしたブロック内に記述します。

callProcs.nim
proc findMax(x: int, y: int): int =  [1]
  if x > y:
    return x                         [2]
  else:
    return y
  # ここはプロシージャの中
# ここはプロシージャの外
  • [1] findMaxというプロシージャの宣言で、xyの2つの引数を持ち、int型を返します。
  • [2] プロシージャから値を返すには、returnキーワードを使用します。
proc echoLanguageRating(language: string) =    [1]
  case language
  of "Nim", "nim", "NIM":
    echo language, " は最高の言語です!"
  else:
    echo language, " は二番目に良い言語でしょう。"

* [1] echoLanguageRating プロシージャは、指定された名前を出力するだけで何も返しません。したがって、戻り値の型は宣言されません。

通常、渡された引数は変更できません。このようなことをするとエラーになります。

proc changeArgument(argument: int) =
  argument += 5

var ourVariable = 10
changeArgument(ourVariable)

上記を動作させるには、Nimと、プロシージャを利用するプログラマが、引数を変数として宣言して変更できるようにする必要があります。

proc changeArgument(argument: var int) =    [1]
  argument += 5

var ourVariable = 10
changeArgument(ourVariable)
echo ourVariable
changeArgument(ourVariable)
echo ourVariable
  • [1] 引数が単にintではなくvar intで宣言されていることに注目してください。
15
20

当然、渡す側の名前も変数として宣言する必要があり、constまたはletを使って代入したものを渡すとエラーになります。

引数として値を渡すことは良い習慣ですが、プロシージャの外側で宣言された名前(変数と定数の両方)を使うこともできます。

var x = 100

proc echoX() =
  echo x        [1]
  x += 1        [2]

echoX()
echoX()
  • [1] 外部の変数xにアクセスしています。
  • [2] 変数として宣言されているので、値を変更することも可能です。
100
101

プロシージャの呼び出し

プロシージャはを宣言した後に呼び出すことができます。下記のように名前を宣言し、引数を括弧で囲むのが、多くのプログラミング言語でプロシージャや関数を呼び出す基本の方法です。

<プロシージャ名>(<引数1>, <引数2>, ...)

プロシージャを呼び出した結果は変数に格納できます。

上記の例のfindMaxプロシージャを呼び出して戻り値を変数に保存する場合は、次のようにします。

callProcs.nim
let
  a = findMax(987, 789)
  b = findMax(123, 321)
  c = findMax(a, b)     [1]

echo a
echo b
echo c
  • [1] 関数findMaxの結果はここではcと名付けられ、最初の2回の呼び出しの結果を使って呼び出されます(findMax(987, 321))。
987
321
987

Nimは他の多くの言語とは異なり、Uniform Function Call構文もサポートしています。これにより様々な方法でプロシージャを呼び出すことができます。

これは、最初の引数が関数名の前に書かれ、残りのパラメータが括弧内に記述されている呼び出し方です。

<引数1>.<プロシージャ名>(<引数2>, ...

既存のシーケンスに要素を追加するとき(<seq>.add(<element>))に、この構文を使用しています。add(<seq>, <element>)と書くより読みやすくなり、意図を明確に表すためです。なお、以下のように、引数を囲む括弧は省略できます。

<プロシージャ名> <引数1>, <引数2>, ...

echoプロシージャを呼び出すときや、引数なしでlenプロシージャを呼び出すときにこの書き方が使用されています。この2つは下記のように結合することができますが、この構文はあまり見られません。

<引数1>.<プロシージャ名> <引数2>, <引数3>, ...

Uniform Function Call構文により、複数のプロシージャを読みやすくつなげることができます。

ufcs.nim
proc plus(x, y: int): int =   [1]
  return x + y

proc multi(x, y: int): int =
  return x * y

let
  a = 2
  b = 3
  c = 4

echo a.plus(b) == plus(a, b)
echo c.multi(a) == multi(c, a)


echo a.plus(b).multi(c)       [2]
echo c.multi(b).plus(a)       [3]
  • [1] 複数の引数が同じ型である場合は、コンパクトに型を宣言できます。
  • [2] 最初にabを加算し、演算結果(2 + 3 = 5)が1つ目の引数としてmultiプロシージャに渡され、cが乗算(5 * 4 = 20)されます。
  • [3] 最初にcbを乗算し、演算結果(4 * 3 = 12)が1つ目のパラメーターとしてplusプロシージャに渡され、が加算(12 + 2 = 14)されます。
true
true
20
14

戻り値

Nimでは、値を返すすべてのプロシージャはresult変数を持っており、暗黙的に宣言され(初期値で)初期化されています。プロシージャは、return文がなくても、インデントされたブロックの終わりに達すると、result変数の値を返します。

result.nim
proc findBiggest(a: seq[int]): int =  [1]
  for number in a:
    if number > result:
      result = number
  # プロシージャの終わり                 [2]        

let d = @[3, -5, 11, 33, 7, -15]
echo findBiggest(d)
  • [1] 戻り値の型はintです。result変数は、デフォルト値int: 0で初期化されます。
  • [2] プロシージャの終わりに達すると、resultの値が返されます。
33

注意! Nimの旧バージョン(Nim 0.19.0以前)では、文字列とシーケンスのデフォルト値はnilでした。これらを戻り値の型として使用する場合は、result変数を空の文字列("")または空のシーケンス(@[])で初期化する必要があります。

result.nim
proc keepOdds(a: seq[int]): seq[int] =
  # result = @[]                            [1]
  for number in a:
    if number mod 2 == 1:
      result.add(number)


let f = @[1, 6, 4, 43, 57, 34, 98]
echo keepOdds(f)
  • [1] Nimバージョン0.19.0以降ではこの行は不要です。シーケンスは自動的に空のシーケンスで初期化されます。古いNimバージョンでは、シーケンスは初期化する必要があり、この行がなければコンパイラエラーになります。(resultは既に暗黙のうちに宣言されているので、varは使用してはいけないことに注意してください。)
@[1, 43, 57]

プロシージャの中で別のプロシージャを呼び出すこともできます。

filterOdds.nim
proc isDivisibleBy3(x: int): bool =
  return x mod 3 == 0

proc filterMultiplesOf3(a: seq[int]): seq[int] =
  # result = @[]                        [1]
  for i in a:
    if i.isDivisibleBy3():              [2]
      result.add(i)


let
  g = @[2, 6, 5, 7, 9, 0, 5, 3]
  h = @[5, 4, 3, 2, 1]
  i = @[626, 45390, 3219, 4210, 4126]

echo filterMultiplesOf3(g)
echo h.filterMultiplesOf3()
echo filterMultiplesOf3 i               [3]
  • [1] 繰り返しになりますが、この行は新しいバージョンのNimでは不要です。
  • [2] 宣言済みのプロシージャを呼び出しています。戻り値の型はboolであり、if文で使用できます。
  • [2] プロシージャを呼び出す3番目の方法は、すでにお伝えしました。
@[6, 9, 0, 3]
@[3]
@[45390, 3219]

前方宣言

この章の冒頭で述べたように、コードブロックがなくてもプロシージャを宣言できます。プロシージャを呼び出す前に宣言しなければならないためです。以下は動作しません。

echo 5.plus(10) # error       [1]

proc plus(x, y: int): int =   [2]
  return x + y
  • [1] plusはまだ定義されていないので、例外をスローします。
  • [2] ここでplusを定義していますが、Nimはすでに呼び出された後だとわかりません。

これを回避する方法は、前方宣言と呼ばれるものです。

proc plus(x, y: int): int    [1]

echo 5.plus(10)              [2]

proc plus(x, y: int): int =  [3]
  return x + y
  • [1] ここで、定義されたplusプロシージャの存在を明示します。
  • [2] これで、コード内で自由に使用できます。これは動作します。
  • [3] ここが、plusが実際に実装された場所で、もちろん以前の定義と一致しなければなりません。

演習

  1. 引数で渡された名前を受け取って、その人に挨拶するプロシージャを("Hello <名前>"を出力して)作成してください。名前をいくつか格納したシーケンス作成してください。作成したプロシージャを使用して各人に挨拶をしてください。

  2. 3つのうち最大の値を返すプロシージャfindMax3を作成してください。

  3. 平面内の点は、tuple[x, y: float]で表すことができます。2つの地点を受け取るプロシージャを作成し、x同士、y同士の値を加算した、新しい地点を返してください。

  4. 2つのプロシージャticktockを作成し、それぞれ「tick」と「tock」という単語を出力させてください。実行回数を記憶させるためグローバル変数を用意し、カウンタが20になるまで順番に実行します。「tick」と「tock」が交互に20回繰り返されるはずです。 ヒント:前方宣言を使用)

無限ループになった場合は、Ctrl+Cを押せばプログラムの実行を停止できます。

さまざまな引数を渡して呼び出し、すべてのプロシージャを検証してください。

モジュール

これまで、新しいNimファイルを始めるたびに、デフォルトで利用可能な機能を使用してきました。これらの機能はモジュールで拡張することができ、ある特定のトピックに対して機能を追加できます。

最も使用されているNimのモジュールは以下の通りです。
* strutils: 文字列を扱うときの追加機能
* sequtils: シーケンス用の追加機能
* math: 数学の関数(対数、平方根、…)、三角法(sin、cos、…)
* times: 測定したり時間を扱う

しかし、標準ライブラリ内にも、nimbleパッケージマネージャ内にも、多くのモジュールがあります。

モジュールのインポート

モジュールとその全機能をインポートしたい場合は、import <moduleName>をファイルに記述するだけです。コードが使用するモジュールを把握しやすくするため、一般的は先頭に記述します。

stringutils.nim
import strutils                     [1]

let
  a = "My string with whitespace."
  b = '!'

echo a.split()                       [2]
echo a.toUpperAscii()                [3]
echo b.repeat(5)                     [4]
  • [1] strutilsのインポート。
  • [2] strutilsモジュールのsplitを使用。文字列をの単語に分割しています。
  • [3] toUpperAsciiはすべてのASCII文字を大文字に変換しています。
  • [4] repeatstrutilsモジュールのもので、指定された回数だけ文字または文字列全体を繰り返しています。
@["My", "string", "with", "whitespace."]
MY STRING WITH WHITESPACE.
!!!!!
maths.nim
import math                   [1]

let
  c = 30.0 # degrees
  cRadians = c.degToRad()     [2]

echo cRadians
echo sin(cRadians).round(2)   [3]

echo 2^5                      [4]
  • [1] mathをインポート。
  • [2] degToRadを使って角度をラジアンに変換しています。
  • [3] sinはラジアンを取ります。 その結果を(やはりmathモジュールで)丸めて小数点以下2桁までにします。(さもなければ、結果は0.4999999999999999になります)
  • [4] mathモジュールには、べき乗を計算するための^演算子があります。
0.5235987755982988
0.5
32

独自に作成

たいてい、プロジェクトのコード量は非常に膨大で、特定の処理を実行する部分に分割するのは理にかなっています。フォルダ内に2つのファイル(firstFile.nimsecondFile.nimと呼ぶことにします)が共存する場合、一方をモジュールとしてインポートしましょう。

firstFile.nim
proc plus*(a, b: int): int =  [1]
  return a + b

proc minus(a, b: int): int =  [2]
  return a - b
  • [1] plusプロシージャの名前の後にあるアスタリスク(*)に注意してください。これにより、別のファイルでもこのプロシージャが利用可能になります。
  • [2] 対照に、これはこのファイルをインポートしても利用できません。
secondFile.nim
import firstFile            [1] 

echo plus(5, 10)            [2]
echo minus(10, 5) # error   [3]
  • [1] ここでfirstFile.nimをインポートしています。.nim拡張子は不要です。
  • [2] これは正常に動作し、firstFileが利用できるように宣言されているので15を出力します。
  • [3] minusプロシージャは名前の後ろにアスタリスクがなく、利用不能であるため、エラーになります。

ユーザ入力との対話

これまでに紹介したもの(基本的なデータ型とコンテナ、制御構文、ループ)を使用して、簡単なプログラムを作成できるようになりました。プログラムをより対話的にするためには、ファイルからデータを読むか、ユーザーに入力を求めるオプションが必要です。

ファイル読み込み

Nimコードと同じディレクトリにpeople.txtというテキストファイルがあるとします。内容は下記のとおりです。

people.txt
Alice A.
Bob B.
Carol C.

プログラム内でファイルの内容を、名前のリスト(シーケンス)として使いたいのです。

readFromFile.nim
import strutils

let contents = readFile("people.txt")   [1]
echo contents

let people = contents.splitLines()      [2]
echo people
  • [1] ファイルの内容を読み込むには、readFileプロシージャを使用し、読み込むファイルのパスを指定します(ファイルがNimプログラムと同じディレクトリにある場合は、ファイル名だけで十分です)。結果は複数行の文字列です。
  • [2] 複数行の文字列を文字列に分割するには(各文字列の内容1行すべて)、strutilsモジュールのsplitLinesを使用します。
Alice A.
Bob B.
Carol C.
                                            [1]
@["Alice A.", "Bob B.", "Carol C.", ""]     [2]
  • [1] 元のファイルの最後に新しい行(末尾の空行)がありました。これも出力されます。
  • [2] そのため、シーケンスは想定より長いです。

最後の新しい行は、ファイルを読み込んだ後にstrutilsからstripプロシージャを使用することで解決できます。これは、文字列の先頭と末尾から空白を削除するだけです。空白文字は、単にスペース、改行、スペース、タブなどを作る任意の文字です。

readFromFile2.nim
import strutils

let contents = readFile("people.txt").strip()   [1]
echo contents

let people = contents.splitLines()
echo people
  • [1] strip を使えば期待通りの結果が得られます。
Alice A.
Bob B.
Carol C.
@["Alice A.", "Bob B.", "Carol C."]

ユーザ入力の読み込み

ユーザと対話したい場合は、入力を受け付け、処理および使用することができなければなりません。標準入力(stdin)から読み込むには、readLineプロシージャにstdinを渡す必要があります。

interaction1.nim
echo "Please enter your name:"
let name = readLine(stdin)        [1]

echo "Hello ", name, ", nice to meet you!"
  • [1] nameの型は文字列だとみなされます。
Please enter your name:
      [1]     
  • [1] ユーザー入力を待っています。名前を入力してEnterキーを押すと、プログラムは続行されます。
Please enter your name:
Alice
Hello Alice, nice to meet you!

VS Codeユーザーは通常の方法(Ctrl + Alt + N)で実行することができません。出力ウィンドウではユーザーの入力が許可されていないためです。端末で実行する必要があります。

数字の扱い

ファイルまたはユーザ入力から読み取ると、常に文字列が返ります。数値を使用したい場合は、文字列を数値に変換する必要があります。再度strutilsモジュールのparseIntを使用して整数に変換するか、parseFloatを使用して浮動小数点数に変換します。

interaction2.nim
import strutils

echo "生まれた年を入力してください:"
let yearOfBirth = readLine(stdin).parseInt()  [1]

let age = 2018 - yearOfBirth

echo "あなたは ", age, " 歳です。"
  • [1] 文字列を整数に変換します。これで、安心して有効な整数を取得できます。もしユーザーが'79ninety-threeを入力するとどうなりますか? 自分で試してみてください。
Please enter your year of birth:
1934
You are 84 years old.

Nimコードと同じディレクトリに、以下の内容のnumbers.txtファイルがあるとします。

numbers.txt
27.3
98.24
11.93
33.67
55.01

このファイルを読み込み、数の合計と平均を求めたい場合は、このようにします。

interaction3.nim
import strutils, sequtils, math        [1]

let
  strNums = readFile("numbers.txt").strip().splitLines()  [2]
  nums = strNums.map(parseFloat)       [3]

let
  sumNums = sum(nums)                  [4]
  average = sumNums / float(nums.len)  [5]

echo sumNums
echo average
  • [1] 複数のモジュールをインポートしています。strutilsstripsplitLinesが使え、sequtilsmapが使え、mathsumが使えるようになります。
  • [2] 最後の新しい行を取り除き、行を分割して文字列にしています。
  • [3] mapは、コンテナの各メンバにプロシージャー(この場合はparseFloat)を渡して使用します。つまり、各文字列を浮動小数点数に変換して、新しい浮動小数点数型のシーケンスを返します。
  • [4] mathモジュールのsumを使用して、シーケンス内のすべての要素の合計を取得します。
  • [5] sumNumsが浮動小数点数なので、シーケンスの長さを浮動小数点数に変換する必要があります。
226.15
45.23

演習

  1. 身長と体重をユーザに尋ねて、BMIを計算してください。BMI値とカテゴリーを出力してください。

  2. コラッツの問題の演習をやり直してください。ユーザーに数を尋ねるようにし、結果のシーケンスを出力してください。

  3. 反転したい文字列をユーザに尋ねます。文字列を受け取り、反転したものを返すプロシージャを作成してください。たとえば、ユーザーがNim-langと入力した場合、プロシージャはgnal-miNを返す必要があります。(ヒント:インデックスとカウントダウンを使う)

結論

このチュートリアルを締めくくる時間です。願わくば、これが有益でありますように。そしてプログラミング・Nimプログラミング言語での第一歩となりますように。

これらは基本的なものであり、かじっただけですが、簡単なプログラムを作成し、いくつかの簡単なタスクやパズルを解くことができるようになるはずです。 Nimにはもっとたくさんの提供すべきものがあり、願わくば、その可能性を探求し続けますように。

次のステップ

Nimのチュートリアルから学び続けたい場合は、

プログラミングパズルを解きたい場合は、

  • Advent of Code:: 毎年12月に公開される面白いパズルシリーズ。過去のパズルのアーカイブ(2015年以降)が利用可能です。
  • Project Euler: 主に数学のタスクです。

ハッピーコーディング!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away