前書き
第1章 Nimをはじめよう
第2章 基本文法
第3章 オブジェクト指向プログラミング
第4章 パッケージ開発
第5章 型
第6章 プラグマ
第7章 メタプログラミング
第8章 数値
第9章 数学
第10章 文字列
第11章 マルチスレッドプログラミング
第12章 ビット操作
第13章 集合
第14章 アルゴリズム
第15章 動的配列処理
第16章 外部関数インターフェイス
付録A コンパイラ
付録B INim
この度は『プログラミングNim』を手に取っていただき、ありがとうございます。
私がNimと出会ったのは2018年、中学3年生の頃です。一見スクリプト言語のようで書きやすく、しかし高速で型に守られたこの言語は、一瞬で私を虜にしました。あれから3年も経ちましたが、現在でもメイン言語として、ライブラリやOSの開発に取り組んでいます。
トランスパイル言語として、CやC++、Objective-C、JavaScriptに変換され、ネイティブなコンパイルは行わないという、思い切ったように見える方針のNim。他言語インターフェースによって資産を流用し、速度を担保し、その上でモダンな文法、表現力の高い様々な機能を実現しています。
Nimは宣伝力に欠け、大企業による支援も多くは受けていないため、人々に、特に日本人の間ではあまり広まっていない印象があります。私はNimを広めるために、技術記事投稿サイトなどに知見を共有し始めましたが、体系性に欠けているため、同人出版することにしました。その後、運よく商業出版のお話をいただき、この本を書き上げることができました。
『Nim in Action』という名著がある中で、商業出版をすることには少し迷いもありました。この本はもともとプログラミング初学者の方に、Nimを第一言語として使ってもらいたいという思いで書こうと考えていました。しかし蓋を開けてみると、思ったよりニッチな内容で占められてしまいました。そこで思い切って初心者向けの記述を全て省き、プログラミング経験者にNimの面白いところをいくつか知ってもらえればいいな、という方針に切り替えました。学ぶこと、というよりは読み物として、面白い知識などを十分含められたと思います。
ぜひ、この本を通して、なんとなくNimって面白いな、興味が出たな、と思った方は、少しだけコードを書いてみてください。今まで書いていたスクリプト、Nimでささっと書くと、想像以上に高速化されるかもしれません。できあがったスクリプトは、Nimble package directoryに公開してみてください。この本をきっかけに日本人Nimmerが増えたとしたら、これほど喜ばしいことはありません。
ご質問などがございましたら、こちらへご連絡ください。
Twitter: @momeemt
Gmail: momiimt@gmail.com
私がDiscordで運営している「ゆるいNimサーバー」ですが、とてもゆるくNimmerが交流しています。2021年6月には、初のLT会が開催されました。よければ参加してみてくださいね。
Discord: https://discord.gg/3umT33kQhS
Nimは、2006年にAndreas Rumpf氏が開発を開始し、2008年に公開されたプログラミング言語です。2019年9月23日に、13年の時を経てversion 1.0がリリースされました。さまざまなバグや不具合が修正されたほか、サードパーティ製のライブラリがリリースされ、言語仕様もどんどん洗練されて、今日に至ります。
公式サイトではEfficient, expressive, elegantと謳っているほど、効率的で表現豊かに、エレガントなプログラムを書くのにぴったりな言語です。
本書は、技術書典10で頒布した「Nim XD Book1」「Nim XD Book2」を統合し、大幅に加筆修正を行った、プログラミング言語Nimについての技術書です。2021年6月、現在の最新バージョンであるversion.1.4.8を基に執筆しました。あらかじめ、本書の構成についての解説を行います。
・「Nimをはじめよう」では、本書の構成とNimの概要、環境構築、周辺ツールについて解説を行っています。
・「基本文法」では、Nimをすぐに書き始められるよう、最低限の文法事項について説明しています。
・「オブジェクト指向プログラミング」では、最低限に留めるべきとされるNimのオブジェクト指向機能について軽く説明しています。
・「メタプログラミング」では、Nimの最大の特徴ともいえる強力なメタプログラミングについて解説しています。
・「パッケージ開発」では、実際に手を動かしながら、Nimbleパッケージを開発する手順を説明しています。
・「型」では、静的型付け言語のNimにおける型について説明しています。
・「プラグマ」では、Nimで組み込みで実装されているプラグマの使い方について、大まかに触れています。
・「数値」では、Nimの数値リテラルから乱数などのトピックについて触れています。
・「数学」では、有理数や複素数、統計分析を扱う標準ライブラリについて触れています。
・「文字列」では、Nimの文字列の特徴や関連ライブラリについて触れています。
・「マルチスレッドプログラミング」では、Nimの低レベルマルチスレッドを扱うthreadとchannelについて触れています。
・「ビット操作」では、基本的なビット操作や関連ライブラリについて触れています。
・「集合」では、HashSetという効率的な集合について触れています。
・「アルゴリズム」では、標準ライブラリとして提供されているアルゴリズムについて触れています。
・「動的配列処理」では、seq[T] 型に対する扱い方や処理について触れています。
・「外部関数インターフェース」では、C言語の連携を中心に触れています。
一貫した入門テキストというよりは、Nimに触れている方が、新しい機能やライブラリについての知見を深められるような構成にしています。サンプルソースもしっかり載せておりますので、リファレンスとしても活用しやすいかと思います。
Unix系のオペレーションシステムを利用しているか、Windowsを利用しているか、その他のOSを利用しているかで方法が異なります。また、自動インストールと手動インストールがあり、基本的には前者が推奨されていますが、何か問題が発生した場合には、手動で環境を整えることも可能になっています。
Nimのコア開発メンバーのdom96氏が開発した、choosenimを使ってインストールします。これは、複数の安定版と開発版を簡単に切り替えることのできる環境管理ツールです。
1: curl https://nim-lang.org/choosenim/init.sh -sSf | sh
choosenimのインストールと同時に、Nimの最新版がダウンロードされます。その後、パスを通してコマンドラインから呼び出せるようにしておきましょう。
1: export PATH=<HOME PATH>/.nimble/bin:$PATH
Windowsの場合は、Nimの公式サイト1でインストーラーが配布されているので、それを用いてインストールします。起動すると、バイナリとNimのインストールディレクトリにパスを通すかどうかを聞いてくれるので、まとめてチェックしてください。また、NimはPCREsとOpenSSLに依存しているので、それらをインストールできる動的リンクライブラリをダウンロード2し、nim.exeと同じディレクトリに配置してから、インストーラーを起動してください。
各OSに付属するパッケージマネージャーを用いて、インストールすることもできます。
1: pacman -S nim
1: apt-get install nim
ユーザーコミュニティがコンパイラとNimbleが含まれたDockerイメージを管理しており、Docker Hubで公開されています。
1: docker pull nimlang/nim
1: docker pull nimlang/nim:devel
1: dnf install nim
1: pkg install nim
1: brew install nim
1: pkg_add nim
1: zypper in nim
1: snap install nim-lang --classic
1: snap install nim-lang-lts-1 --classic
1: snap install nim-lang-nightly --classic
1: xbps-install -S nim
何か問題があった場合には、手動でインストールすることもできます。自動インストールできた場合には、読み飛ばしていただいて構いません。
まずは、公式サイトからx86_64バイナリ(64bit)3かx86バイナリ(32bit)4をダウンロードしてください。
解凍して該当ディレクトリに移動したのち、次のコマンドを実行してください。
1: sh build.sh bin/nim c koch ./koch tools
2: bin/nim c koch
3: ./koch tools
その後、binディレクトリと<HOMEPATH>/.nimble/binのパスを通してください。
トランスパイルしたC言語のソースを実行するために、Cコンパイラを用意してください。macOSではデフォルトでclangというコンパイラが内蔵されているので、最新バージョンをインストールします。
1: xcode-select --install
Linuxでは、すでに何らかのコンパイラがプリインストールされているはずですが、ない場合はgccやclangなどのコンパイラを用意してください。
Nimのコンパイラの大まかな使い方について解説します。
コンパイラの重要な機能のひとつとして、コンパイルがあります。
Nimbleは、Nimのパッケージ管理機能とビルド機能を兼ね備えたソフトウェアです。Nimのインストールと同時にNimbleもインストールされ、利用可能になります。まずは、使えるかどうかを確かめてみましょう。
1: nimble --version
1: nimble v0.13.1 compiled at 2021-04-18 07:37:24
もしここでNimbleがインストールされていなかった場合は、「付録C? Nimble」で入手方法を解説していますので、先にそちらを済ませてください。
Nimで書かれたライブラリやアプリケーションは、Nimbleを通して第三者に公開したり、第三者が書いたものをインストールできます。コマンドラインやGUIなどから利用するために、バイナリの状態で入手するパッケージをバイナリパッケージ、自分のNimプログラムから呼び出すために、ソースコードの状態で入手するパッケージをライブラリパッケージ、そのどちらも行うために、バイナリとソースコードを一緒に入手するパッケージをハイブリッドパッケージと呼んでいます。
ここでは、バイナリパッケージをダウンロードして、動作させてみましょう。
1: nimble install inim
バイナリは、~/.nimbleにダウンロードされているので、コマンドラインから利用するには、パスを通す必要があります。
Nimbleパッケージとして公開できるのは、Nimbleプロジェクトとして正しい構造でなければなりません。
1: nimble init helloworld
ここで、Nimやその周辺環境の歴史についても学んでおきましょう。
NimはもともとNimrodという名前の言語でした。Nimrodは旧約聖書の登場人物で、『ユダヤ古代誌』では、バベルの塔の建設を命じた王であるとされています。ver.0.10.2のリリース時に、言語名がNimrodからNimに変わりました。
もともと、コンパイルはPascalで実装されていました。2008年にセルフコンパイルが達成されて、現在はNimで書かれています。約90000行のコードで構成されており、さまざまな言語機能が実装されています。
開発者であるAndreas Rumpf氏の当初の目標はC言語と同じくらい高速で、Pythonのように表現豊かで、Lispと同じくらい拡張性が高いことでした。具体的には、
・メタプログラミングによって言語を小さく保てること
・C言語にトランスパイルできること
・20000行のソースコードによって実装すること
・C言語やC++、Adaの失敗を活かすこと
などが挙げられますが、15年経過した現在から見ると、おおよそ達成されているように見えます。Nimがどのように進化してきたかは、公式ブログ5でver.0.8.6(Nimrod時代)のリリース記録から知ることができます。
2020年には、はじめての公式カンファレンスがオンラインで開催されました。15個の講演がYouTube6で配信され、現在も視聴できます。
・初心者のためのNimの紹介(Dominik Picheta氏)
・Nicoを用いてさっとゲームを開発する(Jez Impbox氏)
・INimが帰ってきました!(Tristram Oaten氏)
・埋め込み NimScript(Peter Munch-Ellingsen氏)
・低レベルなオーディオプログラミングをサポートするドメイン固有言語 Omni(Francesco Cameli氏)
・NimでLチカを実装する ESP8266プログラミング(Christian Jacobsen氏)
・Nimによるゲームボーイアドバンス開発(Jeremy Clarke氏)
・ARC/ORC(Andreas Rumpf氏)
・NimによるAndroid apkファイルのアセンブル Dali(Mateusz Czapliński氏)
・マルチスレッドプログラミング(Mamy André-Ratsimbazafy氏)
・Decimal128の使いどき・使い方(John Dupuy氏)
・動的サイトジェネレーター Gerbil(Jason Jones氏)
・Jesterプラグイン(John Dupuy氏)
・NimによるUI開発を考え直そう Fidget(Andre von Houck氏)
・3Dライブコーディング Enu(Scott Wadden氏)
Nimの開発状況を表1.1で見てみましょう。
バージョン | 更新日時 |
ver.1.4.6 | 2021/04/15 |
ver.1.2.12 | 2021/04/15 |
ver.1.4.4 | 2021/02/23 |
ver.1.2.10 | 2021/02/23 |
ver.1.4.2 | 2020/12/01 |
ver.1.2.8 | 2020/10/27 |
ver.1.0.10 | 2020/10/27 |
ver.1.4.0 | 2020/10/16 |
ver.1.2.6 | 2020/07/30 |
ver.1.0.8 | 2020/07/30 |
開発は活発で、おおよそ2か月程度でパッチリリースが公開されます。また、choosenimで開発バージョン(nightlies)を手に入れることができます。
1: choosenim devel
1: Downloading Nim 2021-04-21-devel-eb221dcc27b7f8f11ec34046e3165508acdf8beb from GitHub
2: [##################################################] 100.0% 0kb/s
3: Extracting nim-1.5.1-macosx_x64.tar.xz
4: Setting up git repository
5: Building Nim #devel
6: Compiler: Already built
7: Tools: Already built
8: Installed component 'nimble'
9: Switched to Nim #devel
バージョンを確認すると、飛ばされている奇数バージョンはnightliesであることがわかります。
1: nim -v
1: Nim Compiler Version 1.5.1 [MacOSX: amd64]
2: Compiled at 2021-04-21
3: Copyright (c) 2006-2021 by Andreas Rumpf
Nimは、空白文字(スペース)のインデントによってコードブロックが形成される、インデントブロックを採用しています。著名なプログラミング言語であるPythonもこれを導入しており、Nimを知らない多くのプログラマにとっては、NimがPythonのように映るかもしれません。しかし、決定的な違いは、Pythonが動的型付け言語であるのに対し、Nimは静的型付け言語であるという点です。近年ではコンパイル時に型が決定することの利点が注目され、Pythonでもver.3.5から型ヒントが実装されました。Nimの強力な点は、単に型を決定するだけでなく、コンパイル時定数やメタプログラミングで、複雑なコンパイル時計算が簡単に実行可能であることです。
多くの教育機関で教材として採用されるなど、Pythonの学習ハードルの低さは周知の事実だと思いますが、Nimも入門するための言語仕様は、同等の難易度だと考えて差し支えありません。まずは本章で、先ほどインストールしたNimの基本的な文法事項について、勉強していきましょう。
ある識別子を与えて、識別可能になった書き換え可能な記憶域を変数といいます。それに対して、一度データを与えてしまうと書き換え不可能な記憶域を不変変数と呼びます。
変数はvarを、不変変数はletを用いて、次のように定義できます。
1: var 変数名: データ型 = 値
2: let 不変変数名: データ型 = 値
変数名name、データ型がstring、値がmomiyamaである変数を定義しなさい。また、不変変数名value、データ型がint、値が100である不変変数を定義しなさい。
1: var name: string = "momiyama"
2: let value: int = 100
"momiyama"のような、ダブルクォーテーションで囲まれた字句要素を文字列リテラルといいます。また、'x'のように、シングルクォーテーションで囲まれた文字を文字リテラルと呼びます。これは、変数名などの識別子と文字や文字列を区別するために必要です。また、文字列リテラルの値のデータ型をstring、文字リテラルの値のデータ型をcharといいます。データ型については、この後説明します。
100、256.256などのように、直接数値を表す字句要素を数値リテラルと呼びます。小数点を含まない整数の数値リテラルのデータ型はint、小数のデータ型はfloatです。
変数名practice_1、データ型がstring、値がanswer_1である変数を定義しなさい。
多くのコンピューターには、とくに明示しなければデフォルトで利用されるコンピューターへの入力・出力が存在します。これを標準入出力と呼びます。これからはキーボードによる打ち込みが標準入力、ターミナルやコマンドプロンプトへの出力が標準出力だと決められている環境下で話が進みますが、常にそうなるわけではありません。
標準出力とは、コンピューターがデフォルトでデータの出力先に指定する、出力装置や出力システムのことです。多くの汎用コンピューターではコンソールへの文字表示が設定されていますが、ファイルの書き出しなどに変更できます。echoプロシージャは、標準出力に書き込んでフラッシュflashする機能を提供します。
1: echo "Hello, ", "world!"
1: Hello, world!
echoは可変長パラメーターを受け取ります。その上、文字列に変換可能な任意の型のデータを受け取って、stringにキャスト(具体的にいえば$演算子を適用)して出力を行います。
1: echo 2, 0, 2, '1', "年 おめでとう!"
1: 2021年 おめでとう!
echoは、JavaScriptバックエンドに対しても書くことができます1。ところで、デバッグに頻繁に用いられるため、スレッドセーフであることが特徴的です。とはいえIOなので、副作用であることには変わりなく、func文などにおいてデバッグすることができません。
そのような場合は、debugEchoプロシージャを利用しましょう。
echoプロシージャに対して、文字列リテラルだけでなくstring型の変数を指定することで出力できます。また、カンマで区切ることで、複数のシンボルを一気に出力できます。さらに、string型以外のデータに対しては暗黙に文字列型に変換され、出力できます。
1: var name: string = "momiyama"
2: var id: string = "@momeemt"
3:
4: echo "name: ", name
5: echo "id: ", id
6: echo "age: ", 17
name: momiyama
id: @momeemt
age: 17
標準出力に対し、Programming Language - Nimと出力しなさい。
標準入力から値を受け取るためには、stdinオブジェクトに対してreadLineプロシージャを呼び出します。
1: var input = stdin.readLine
静的型付け言語にもかかわらず、型情報が見当たりません。Nimは、ユーザーに与えられた値から型を推測する型推論が実装されており、多くの場合で型宣言を省略できます。
1: var country = "Japan"
2: let age = 17
typeofプロシージャを使って、値の型が何かを調べることができます。変数countryと不変変数ageがどう推論されているか、確かめてみましょう。
リスト2.9 における変数countryと不変変数ageの型を標準出力に出力しなさい。
1: echo typeof country
2: echo typeof age
string
int
Nimには変数と不変変数の他にも、コンパイル時定数という記憶域を定義できます。
1: const コンパイル時定数名: データ型 = 値
コンパイル時定数は、プログラムがコンパイルされるときに値が決定されます。つまり、コンパイル時に確定しない値を扱うことはできません。コンパイル時に確定しない値など、あるのでしょうか?たとえば、標準入力によって受け取る値は、実行時になるまで確定しません。そのような値を代入しようとした場合は、コンパイル時に静的エラーを検出します。
1: const inputConst = stdin.readLine
複数の変数、不変変数、コンパイル時定数を定義セクションによってまとめて定義できます。
1: var
2: country = "Japan"
3: name = "Momiyama"
4: id = "momeemt"
5: age = 17
このように、インデントを空けてブロック構造を取るものをセクションといいます。
文字リテラルは、組み込み文字型であるchar型に型付けされます。
1: const a: char = 'a'
char型はしばしば、「1文字の文字」を扱う型であると勘違いされますが、正確には「1バイトの文字」を扱う型です。文字コード規格のUnicodeでは日本語が複数バイトで扱われるため、char型で日本語を保持することはできません。試しに代入してみましょう。
1: const hiragana: char = 'あ'
1: Error: missing closing ' for character literal
1バイトで扱える文字は、アルファベットやいくつかの記号のみです。Nim言語でマルチバイト文字を処理する場合はUnicodeモジュールを利用しますが、これは本書の「」で解説します。
文字列リテラルは、組み込み文字列型であるstring型に型付けされます。これはミュータブル、つまり値が変更可能であり、文字をあとから追加・書き換えができます。
1: const greeting: string = "Hello!"
また、三重符文字列リテラルによって、改行を含む文字列を扱うことができます。これはその名の通り「ダブルクォーテーションを3つ使って囲った文字列」であり、ドキュメント作成などに役立ちます。
1: const article: string = """
2: I woke up early today.
3: I dried the laundry.
4: I bought a delicious lunch.
5: I took a shower.
6: A great day!
7: """
&演算子を適用することで、文字や文字列を結合できます。
1: echo "Hi, " & "momeemt!" # 文字列と文字列の結合
2: echo "Hell" & 'o' # 文字列と文字の結合
3: echo 'G' & "ood morning!" # 文字と文字列の結合
4: echo 'A' & 'B' # 文字と文字の結合
上のような結合演算を見て、「文字型と文字列型の間で暗黙の型変換が起こっているではないか!けしからん!」と思った方がいたかもしれませんが、型変換は起こっていません。
「」で解説しますが、Nimの演算子は関数と同じようにオーバーロード可能で、ふたつの値を受け取って演算結果の値を返します。&演算子は、string型をふたつ受け取るものと、char型をふたつ受け取るものと、string型とchar型を受け取るものがそれぞれふたつで、4つの組み合わせで定義されています。
数値リテラルは、整数ならばint型に型付けされます。負の整数を扱えるint型に対し、正の整数のみを扱う型を、uint型といいます。前者は「符号付き整数」と呼ばれ、後者は「符号なし整数」と呼ばれます。
また、小数点を含む整数リテラルはfloat64型に型付けされます。
1: const
2: birthday: int = 2003_1030
3: number: float = 256.1024
整数や浮動小数点数には、四則演算が定義されています。
演算 | 結果 |
+ | 加算 |
- | 減算 |
* | 乗算 |
div | 除算(整数) |
/ | 除算(浮動小数点数) |
mod | 剰余 |
気をつけなければならないのは、除算です。整数の除算演算子 divでは、割り切れなかったとしても、値が切り捨てられて丸められます。浮動小数点数の除算演算子 /では、ある程度正確な小数値を返します。
真偽値は、「正しい」か「正しくない」を表現できる型です。「正しい」場合の値はtrueで、「正しくない」場合の値はfalseであり、ふたつの値だけを持ちます。
真偽値型は、bool型に型付けされます。
1: const
2: value1: bool = true
3: value2: bool = false
関係演算子を用いると、ふたつの値を受け取ってbool型の値を返します。整数型や浮動小数点数型、文字型や文字列型を比較できます。正確にいえば、ある型に関係演算子が定義されていれば、比較できます。
1: echo 1 < 100 # 1は100より小さい : true
2: echo 15 > 20 # 15は20より大きい : false
3: echo 12 <= 99 # 12は99以下 : true
4: echo 20 >= 48 # 20は48以上 : false
5: echo 22 == 22 # 22は22と等しい : true
6: echo 55 != 22 # 55は12と等しくない: true
論理演算子は、高校数学の集合と同様に、論理式の真偽を返します。たとえば、条件Aと条件Bのどちらも正しいか?あるいはどちらかが正しいか?のような問題に対しての真偽を計算できます。
名前 | 演算子 | 効果 |
論理積 | and | すべての対象がtrueのときtrueを返します |
論理和 | or | 対象のうち、少なくともひとつがtrueのときtrueを返します |
排他的論理和 xor 一方がtrue、もう一方がfalseのときtrueを返します |
|
|
論理否定 not trueならfalse、falseならtrueを返します |
|
|
実行時に要素数を変更できない固定長コンテナーのことを、静的配列と呼びます。静的配列はデフォルトでarray型に型付けされます。配列は、複数の要素を格納・管理できるデータ構造で、大量のデータを扱う場合に、非常に便利です。
arr[i]のように記述することで、配列arrのi番目にアクセスできます。このようなiのことを添字、またはインデックスといいます。ただし、添字は1からではなく0から始まるので、注意しなければなりません。
1: var arr: array[4, int] = [1, 2, 3, 4]
2: arr[0] += 5
3: arr[1] += arr[2]
array型は、正確にはarray[I; T]型と定義されています。このようなブランケットに包まれたIやTを型変数といいます。Iは要素数を、Tは格納するデータの型を渡します。変数arrの型は、要素数が4、データ型がintであるarray[4, int]型ですが、これはarray[5, int]型やarray[4, string]型と明確に区別されます。
静的配列に対しても、lenプロシージャを使って要素数を計算できます。
1: var arr2: array[10, int] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2: echo arr2.len
実行時に要素数を変更できるコンテナーのことを、動的配列、またはシーケンスと呼びます。これはデフォルトでseq型に型付けされます。
1: var seq1: seq[int] = @[1, 2, 3, 4, 5]
seq[T]型も、型変数Tを受け取って実体化します。seq[int]型とseq[string]型はまったく異なる型です。
add
数学的な集合を扱うための型が、set[T]型です。ある型が取りうる範囲を全体集合と定めるため、型変数Tを受け取って実体化します。
その型は、int8、int16、uint8、byte-uint16、char、enum、またはそれに準ずる型でなければなりません。なぜなら、実装上の理由で全体集合が広すぎると、具体的には2^16個以上のときにエラーが発生します。
もしそれより広い全体集合を取る集合を定義したい場合には、HashSet[T]型を使いましょう。第3章の「型システム」や第14章の「コレクション」で、詳しく説明します。
1: var alphabet: set[char]
2: alphabet = { 'a' .. 'z', 'A' .. 'Z' }
3: echo alphabet
1: {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
制御構文は、条件分岐、ループに分けられます。
条件分岐は制御構文のひとつで、ある処理を条件によって振り分けることができます。
if文はもっとも基本的な条件分岐構文です。bool型の値を条件に取り、trueの場合に、そのブロックに記述されたプログラムをすべて実行します。if、elif、elseによって分岐し、その後の分岐条件に当てはまったとしても、それらは実行されません。
1: if condition1:
2: statement1
3: elif condition2:
4: statement2
5: else:
6: statement3
case文は、条件分岐構文のひとつです。case、of、elseによって分岐します。if文とは違い、範囲を指定したり、カンマで区切って複数の値が指定できるなど、自由な条件分岐ができます。ただし、case文では、すべての値を網羅しない場合エラーになります。たとえば、文字列の値をすべて網羅することは不可能ですから、当てはまらない値に関しては、else句で実行する必要があります。
1: let number = 0
2: case number:
3: of 0:
4: echo "zero :("
5: of 90..99:
6: echo "almost perfect"
7: of 100:
8: echo "perfect!"
9: else:
10: echo number
when文も、条件分岐構文のひとつです。
しかし、if文やcase文とは、まったく毛色の異なる構文です。これらの条件分岐構文では、条件が実行時に評価されていました。しかし、when文ではコンパイル時に評価されます。そのため、条件は定数式でなければなりません。
条件分岐に当てはまったブロックのみがコンパイルされ、その他の文は実行可能ファイルには残りません。このような特徴から、OS固有のコードや環境変数に依存するコードに用いられています。また、他の条件分岐構文と異なり、スコープは形成されません。
1: when defined(widnows):
2: echo "windows"
3: elif defined(macos):
4: echo "macos"
5: elif defined(linux):
6: echo "Linux"
7: else:
8: echo "unknown operation system :("
ループは、ある処理を繰り返すことができる制御構文です。
Nimのループでもっともポピュラーなのは、for文です。
for文は、繰り返し構造を持つデータから値を取り出し続け、なくなるまで処理を繰り返します。この繰り返し構造をイテレータと呼びますが、説明だけではイメージしづらいので、1から10までの整数を順番に出力する、単純なプログラムについて考えてみましょう。
1: for i in countup(1, 10):
2: echo i
countupイテレータは、ひとつ目の引数からふたつ目の引数までの値を持つ構造です。そこからまず1が取り出されて、ローカル変数iに代入されます。10まで取り出されると空っぽにになるので、その時点でループは終了します。
また、countupイテレータは..イテレータというエイリアス(別名)を持ちます。より直感的な構文で記述できます。
1: for i in 1..10:
2: echo i
while文は、もっとも単純なループ制御構文です。for文のように、イテレータやローカル変数を持ちません。
1: var counter = 0
2: while counter <= 10:
3: echo "yeah!"
4: counter += 1
break文は、スコープから抜けるための制御構文です。
1: for i in 27..100:
2: if i mod 8 == 0:
3: break
4: echo i
27
28
29
30
31
デフォルトでは現在のスコープのみを抜けますが、ラベルを明示することで、そのラベルを持つスコープから抜けることができます。
1: block block1:
2: for i in 7..10:
3: block block2:
4: for j in 13..22:
5: if (i * j) mod 6 == 0:
6: break block1
7: echo i * j
91
98
105
112
119