2016-12-31#チュートリアルの訳の前にNimという言語は、python風の構文を持ち、C言語並みの速度を持つ、プログラミング言語です。Pythonistaにとって夢のような言語です。インストールはこちらがわかりやすいと思います。公式のインストールページはこちらからどうぞ。Nimを勉強するにあたり、その日本語文書の少なさがボトルネックになると思い、手始めに公式チュートリアルを翻訳しました。原文はこちらからこれはその前半(パート1)の前編です。適したアップロード場所や公式の翻訳フォームがあれば教えてください。誤訳や誤解にお気づきの際は、遠慮なくお申し付けください。ツイッターにリプを飛ばすと反応は速いです。サンプルコードは基本的に、ideoneで実行し、リンクを貼るつもりですが、ideoneのNimのバージョンが0.11.2(2016/12/29日現在)ですので、一部手元の実行環境で確認するにとどめています。mir."「人間は視覚に頼る動物だ。私は美しい物が欲しい。」1これはプログラミング言語『Nim』のチュートリアルである。変数・型・文などの、プログラミングの基本的な概念を理解した読者を想定したチュートリアルではあるものの、かなり基本的な事項のみ扱う。発展的な言語仕様の例はマニュアルに多く記載されている。このチュートリアルや、他のNimの文書に記載されているNimのコード例は全て、Nimスタイルガイドに従う。world"に比べたら複雑なコードでNimの旅を始めよう。このコードを"greetings.nim"に保存、コンパイル、実行してみよう。greetings.nim--runスイッチを付けると、コンパイルが終わるとファイルが自動的に実行される。コマンドライン引数は、ファイル名の後に記述する事でプログラムに渡せる。arg2よく使われるコマンドやスイッチは、省略形を持ち、以下のような記述も可能である。greetings.nimrelease版をコンパイルするには、以下のように記述すれば良い。greetings.nimデフォルト状態ではデバッグの為に、膨大な量のランタイムチェックを作る。2-d:releaseにより、これらのチェックは無効化され、最適化が行われる。これが何のプログラムかは明白だろうが、構文の説明をしよう。インデントのない文はプログラム実行時に実行される。インデントにより、Nimは複数の文を一つにまとめる。インデントはスペースのみであり、タブキーによるインデントは受け付けない。文字列リテラルは二重引用符で囲む。var文により、nameという名前のstring型(文字列型)変数を宣言できる。この変数にはreadLineプロシージャの返り値が代入されている。このコードの場合、型の指定を宣言から省いてもよい。いうのも、コンパイラが「readLineプロシージャが文字列型を返す事」を把握しているからである。(これはローカル型推論と呼ばれる。)よって以下の記述も可能である。readLine(stdin)Nimにおける型推定が基本的に、このローカル型推定である事を覚えておいてほしい。これは簡潔さと可読性とが歩み寄った結果である。world"コードには含まれている。これらの組み込まれた識別子は、システムモジュールで宣言されており、これは他の全てのモジュールが暗黙にインポートする。elementsNimの字句要素を詳しく見てみよう。他のプログラミング言語と同じくNimを構成する要素は、(文字列)リテラル、識別子、キーワード、コメント、演算子、その他記号である。literals文字列リテラルは二重引用符"で囲まれる。一方の文字リテラルは単引用符'で囲まれる。特殊文字をエスケープするには\を用いる。\nは改行、\tはタブを意味する。raw文字列リテラルもある。files\nim"raw文字列リテラル中の\はエスケープ文字ではない。文字列リテラルを記述する最後の方法は、long文字列リテラルという第三の方法である。long文字列リテラルは3つの二重引用符に挟まれる。"""複数行にわたって文字列を記述でき、\もエスケープ文字にならない。使用例としては、HTMLコードを埋め込む、などがある。Comments文字列リテラル・文字リテラルの中でなければ、コメントは#で記述できる。ドキュメント・コメントは##が先頭に付く。ドキュメントコメントドキュメント・コメントは特別な存在である。ドキュメント・コメントは抽象構文木6の中に含まれるため、.nimファイル中7の決まった場所にしか記述できないのだ。これにより、シンプルなドキュメンテーション・ツールで事足りる。discard文8とlong文字列リテラルを組み合わせて使うことで、ブロックコメントを書く、という方法もある。"""この中ならインデントの心配をすることなくどんなコードもコメントアウトできる。Numbers数値リテラルの書き方は、他の多くの言語と同じである。アンダーバー_を使って可読性を上げることもできる。statementvar文を使うことで、ローカル変数やグローバル変数を新しく宣言できる。xとyが``int``型であることの宣言var文の後にインデントを入れると、ひとまとまりの変数を並べて宣言できる。statement代入文を使うことで、新しく値を変数やメモリ領域に代入できる。オーバーロード(拡張)・上書き・禁止することは出来ないが、今後のバージョンで変わりうる。複数の変数をまとめて宣言する際、代入演算子を用いると、全ての変数に同じ値を代入できる。`y`を維持しつつ、`x`を42にする。複数の変数をまとめて宣言し代入するときに、プロシージャが呼び出されると、思わぬ動作をすることは覚えておいてほしい。Constants定数は値と強く結びついた識別子14である。定数xは文字列"abc"を含むconstの後にインデントを入れると、ひとまとまりの定数を並べて定義できる。statementlet文はvar文のような働きを持つが、単一代入変数の宣言に用いられる。初期化後、改めて値を代入できないのだ。`x`への代入let文とconst文の違いは以下のとおりである。letとは再代入できない変数である。statements最初の"greetings.nim"プログラムは、全てが順番に実行される3つの文から構成される。これで上手く作れるのは、簡単なアルゴリズムだけである。要するに、分岐や繰り返しも必要だ。statementif文はプログラムを分岐させる一つの方法である。"面白いね、君の名前は名前なのかい。"!"if文では0個以上のelifを入れる事ができ、elseは任意である。ifの略であり、過剰なインデントを回避するために使う。(コード中の""は空の文字列を示す。"面白いね、君の名前は名前なのかい。"かっこいい名前じゃないか。"!"見てわかる通り、ofの後に、コンマ区切りで値を並べることも可能である。17case文は、整数型、他の順序型や文字列型を受けることが出来る。(順序型の説明は少し後にする)整数型やほかの順序型に関しては、範囲指定も可能である。"入力した数字は3か8です"しかし実は上のコードはコンパイル出来ない。それは、nが取りうるすべての値を網羅する必要があるのに、このコードでは0..8の範囲の値しか扱っていないからである。「取りうる全ての整数を書く」事はあまり実用的でない(範囲指定の記法で可能ではあるが)。そのため、このコンパイルエラーを直すには、他の値に関しては特に実行することが無い事をコンパイラーに伝えるのがよい。discard単独のdiscard文8は何も実行するな文である。elseの付いたcase文がエラーを持たない事をコンパイラは知っており、このエラーは消えるのである。全ての文字列型を網羅することは不可能である事は覚えておいてほしい。いうのも、網羅できない事が「case文を文字列で分岐するときはelseが必要」な理由だからである。一般的に、case文は部分範囲型や列挙型で使われる。これらの型の場合、取りうる値を尽くしたかのコンパイル時の確認が役に立つからである。statementwhile文は簡単なループ構造である。新しい変数を宣言している訳ではないため、ここに``var``はないコード例では、ユーザーが何も入力しない場合(Returnばかり押す)、入力するまで名前を聞くためにwhileループを使っている。statementfor文はイテレータから渡される要素をループ処理するための構造である。コード例では、組み込みのcountupイテレータを用いる。count_for.nim(注:コンパイルエラーを吐きます。それぞれ別の行に出力する。組み込み演算子である$は整数型(int)や他のデータ型を文字列型に変換する。変数iはforループにより暗黙に宣言されており、countupがint型を返すため、iも同じ型を持つ。iは1,2,...,10と変わっていく。それぞれのiの値はechoで出力される。以下のコードも同じ操作をする。count_while.nim(注:コンパイルエラーを吐きます。iを1だけインクリメントする。それぞれ別の行に出力する。カウントダウンは同じくらい簡単にできる。(が、同じほどよく使われる訳ではない)countdown.nim(注:コンパイルエラーを吐きます。linesプログラムでカウントアップする事は、かなりよくある事であり、Nimは..という同じ動作をするイテレータを持つ。count_for_withdots.nim(注:コンパイルエラーを吐きます。statement制御文について、まだ話していないことがある。それは「新しいスコープを作る」ということだ。この事は要するに、以下の例ではxはループ外で取得できない、という意味だ。実行できないwhile文やfor文は暗黙にブロックを作ります。識別子は、それが宣言されたブロックからしか見えません。block文は明示的にブロックを作るのに使われます。こちらも動かないブロックのラベル(コード例においてはmyblock)は任意である。statementbreak文を使うとブロックを途中で抜けることができる。break文が抜けることができるブロックはwhile、for、block文によるブロックである。break文により、最も内側のブロックを抜ける事が出来るが、抜けるブロックをラベルで指定出来る。statement他のプログラミング言語と同様、continue文により、直ちに次の反復処理に移る事が出来る。"私の知らないオペレーション・システムです"when文はif文とほとんど同じだが、いくつかの違いを持つ。書かれる条件式は全て、コンパイラが計算できるように定まった値を持たなくてはならない。分岐内の文は新しいスコープを持たない。コンパイラは、構文を解析し、コンパイル時点でtrueだった最初の分岐の中の式のみをコンパイルする。when文は各プラットフォームに適したコードをそれぞれ書くときに便利であり、C言語における#ifdef文と似ている。false:を使った方が良い事がよくある。この方法なら、ネストが可能だからだ。indentation基本的な制御文を押さえたところで、Nimのインデント規則の話題に戻ろう。Nimにおいては、単純文と複合文が区別される。単純文は他の文を含むことができない。単純文に属するのは、代入文やプロシージャ呼び出し文、return文である。if、when、for、whileといった複合文は他の文を含むことができる。「曖昧さ回避」のため、複合文はインデントを必要とするが、単純文はインデントする必要はない。条件式の後に文が二つ続くため、インデントが必要である。false式は通常、値を返し、文の一部である。例として、if文中の条件式が挙げられる。式の中にインデントを入れることで、可読性を高めることも可能である。true経験則として、式中のインデントが良いのは演算子・閉じていない括弧・コンマの後である。括弧とセミコロン(;)を使うことで、式しか許されない場所でも文が使える。Procuduresこれまでのコード例で出てきた、echo、readLineのような新しい命令を定義するには、プロシージャという概念が必要となる。(メソッドや関数と呼ぶ言語もある24)Nimで新しいプロシージャを定義するには、procというキーワードを使う。"デイブ、申し訳ない。"君も私と同じくらい、なにが問題かわかっているようだね。"この例では、yesという名前のプロシージャが登場する。yesはquestionに格納される質問をユーザーにした上で、「はい」やそれに似た返事に対してはtrueを、「いいえ」や似た返事に対してはfalseを返り値として返す。return文があると、直ちにプロシージャから抜ける。boolという構文は、「questionというstring型のパラメータがあり、bool型の値を返すこと」を示す。bool型(ブーリアン型)の話をすると、これは組み込みのデータ型であり、trueとfalseしか有効な値を持たない。if文やwhile文などの条件式はブーリアン型でなくてはいけない。コード例中のquestionは(正式な)25パラメータと呼ばれ、"全ての君の..."の部分はパラメータに渡される引数と呼ばれる。variable返り値を返すプロシージャでは、返り値を表すresultが暗黙に宣言されている。resultの省略形である。出口のreturnが存在しないプロシージャは、終わる際に必ず、自動的にresultの値を返す。result'と再宣言する事は、result変数を同じ名前の普通の変数で覆い隠し、本来のresult変数が持つ機能を失う事を意味する。27result変数はプロシージャの返す型のデフォルト値で初期化28されている。参照型のresult変数は、プロシージャが宣言されたときはnilの値をとるため、状況に応じて、手動で初期化をしなくてはならない。Parametersプロシージャの中身に記述されたパラメータは、定数として扱われる。何も変更しないと、パラメータの値は変えられないのだ。いうのもその方が、より効率的にコンパイラによるパラメータの受け渡しが出来るからである。プロシージャ内でミュータブルな変数を使いたければ、プロシージャ内でvar文を使って宣言しなければならない。パラメータ名は上書き可能であり、それどころか、実は慣用的に用いられる。s[i]呼び出し先の引数をプロシージャで変えるには、varパラメータを使う。yコード例の中のresとremainderがvarパラメータである。varパラメータはプロシージャで変更することが可能であり、呼び出し先にも引数の変更が伝わっている。31上の例では、varパラメータを使うよりも、タプルを返した方が適切であることは覚えておいてほしい。32discard_result.nim(注:yesプロシージャも定義してあります。yes("つまらない質問をしてもよろしいですか?")呼び出したプロシージャやイテレータを宣言した際にdiscardableプラグマが使われていた場合、discardで明示的に示さずとも、返り値を無視できる。今は可能コメントの項目で解説した通り、discard文はブロックコメントを作る時にも使える。argumentsプロシージャが多くのパラメータを持つ事はよくあり、その場合、パラメータの順番が分からなくなる。これは特に、複雑なデータ型を形成するプロシージャでよく起こる。従って、どの引数がどのパラメータに値を渡すのかを明確にするために、プロシージャに渡す引数は名前を付けることができる。800)今や我々はcreateWindowプロシージャを呼び出すのに、名前付き引数を使っているのであり、引数の順番は関係ない。順番引数と名前付き引数を混ぜるのは可能だが、可読性を欠く。true)パラメータが受け取る引数の個数が、丁度1個になる事はコンパイラによって確かめられる。valuescreateWindowプロシージャを使いやすくするには、デフォルト値を持たせればいい。呼び出し時に指定を受けない限り、このデフォルト値が引数として使われる。800)これでcreateWindowはデフォルト値と違う引数だけを指定すればよくなった。デフォルト値を使って、パラメータの型推論が行われることを覚えておいてほしい。"unknown"と書く必要はない。proceduresNimでは、C++のようにプロシージャが上書きできる。bool)プロシージャを呼び出す(toStringが普通はNimの$演算子で書かれることを覚えておいてほしい)どのプロシージャが呼び出されたtoStringとして適切かは、コンパイラが選ぶ。このオーバーロードを解決するアルゴリズムに関しては、ここではなくマニュアルで詳しく扱う。いえ、思いつきもしない嫌なアルゴリズムではなく、比較的簡単な単一化のためのアルゴリズムに基づいている。曖昧なプロシージャ呼び出しに対しては、エラーが返される。OperatorsNimのライブラリでは、オーバーロードが非常によく使われる。一つの理由としては挙げられるのは、+のような演算子それぞれが、まさにオーバーロードされたプロシージャである事だ。aパーサーで解釈できる。中置表記の演算子は引数を必ず2つ取り、前置表記の演算子は必ず一つ取る。後置表記があると、曖昧になるため、後置演算子は出来ない。(b)のどちらを意味するのかが分からない、ということだ。(@b)である事、が言えるようになったのである。ユーザー定義の演算子は作れる。いう独自の演算子を定義することも可能だが、こんな演算子を定義した場合は可読性が損なわれるかもしれない。37演算子の計算の優先順序は、演算子の最初の文字で決まる。マニュアルに詳しい説明がある。