https://qiita.com/KTakahiro1729/items/f4776f3a072c01f9086b
チュートリアルの訳の前に
Nimという言語は、python風の構文を持ち、C言語並みの速度を持つ、プログラミング言語です。
つまり、Pythonistaにとって夢のような言語です。
つまり、Pythonistaにとって夢のような言語です。
Nimを勉強するにあたり、その日本語文書の少なさがボトルネックになると思い、手始めに公式チュートリアルを翻訳しました。原文はこちらからこれはその前半(パート1)の前編です。
もし、より適したアップロード場所や公式の翻訳フォームがあれば教えてください。
もし、より適したアップロード場所や公式の翻訳フォームがあれば教えてください。
誤訳や誤解にお気づきの際は、遠慮なくお申し付けください。ツイッターにリプを飛ばすと反応は速いです。
サンプルコードは基本的に、ideoneで実行し、リンクを貼るつもりですが、ideoneのNimのバージョンが0.11.2(2016/12/29日現在)ですので、一部手元の実行環境で確認するにとどめています。
サンプルコードは基本的に、ideoneで実行し、リンクを貼るつもりですが、ideoneのNimのバージョンが0.11.2(2016/12/29日現在)ですので、一部手元の実行環境で確認するにとどめています。
Nimチュートリアル パート1 Nim Tutorial (Part I)
著者: Andreas Rumpf
バージョン: 0.15.2
バージョン: 0.15.2
はじめに Introduction
これはプログラミング言語『Nim』のチュートリアルである。
変数・型・文などの、プログラミングの基本的な概念を理解した読者を想定したチュートリアルではあるものの、かなり基本的な事項のみ扱う。
より発展的な言語仕様の例はマニュアルに多く記載されている。
このチュートリアルや、他のNimの文書に記載されているNimのコード例は全て、Nimスタイルガイドに従う。
変数・型・文などの、プログラミングの基本的な概念を理解した読者を想定したチュートリアルではあるものの、かなり基本的な事項のみ扱う。
より発展的な言語仕様の例はマニュアルに多く記載されている。
このチュートリアルや、他のNimの文書に記載されているNimのコード例は全て、Nimスタイルガイドに従う。
最初のプログラム The first program
"hello world"に比べたら複雑なコードでNimの旅を始めよう。
# これはコメントである
echo "お名前は? "
var name: string = readLine(stdin)
echo "やあ、", name, "!"
このコードを"greetings.nim"に保存、コンパイル、実行してみよう。
nim compile --run greetings.nim
--run
スイッチを付けると、コンパイルが終わるとファイルが自動的に実行される。
コマンドライン引数は、ファイル名の後に記述する事でプログラムに渡せる。
nim compile --run greetings.nim arg1 arg2
よく使われるコマンドやスイッチは、省略形を持ち、以下のような記述も可能である。
nim c -r greetings.nim
release版をコンパイルするには、以下のように記述すれば良い。
nim c -d:release greetings.nim
デフォルト状態ではデバッグの為に、膨大な量のランタイムチェックを作る。2
これが何のプログラムかは明白だろうが、構文の説明をしよう。
インデントのない文はプログラム実行時に実行される。
インデントにより、Nimは複数の文を一つにまとめる。
インデントはスペースのみであり、タブキーによるインデントは受け付けない。
-d:release
により、これらのチェックは無効化され、最適化が行われる。これが何のプログラムかは明白だろうが、構文の説明をしよう。
インデントのない文はプログラム実行時に実行される。
インデントにより、Nimは複数の文を一つにまとめる。
インデントはスペースのみであり、タブキーによるインデントは受け付けない。
文字列リテラルは二重引用符で囲む。
この変数にはreadLineプロシージャの返り値が代入されている。
このコードの場合、型の指定を宣言から省いてもよい。
というのも、コンパイラが「readLineプロシージャが文字列型を返す事」を把握しているからである。(これはローカル型推論と呼ばれる。)
よって以下の記述も可能である。
var
文により、name
という名前のstring
型(文字列型)変数を宣言できる。この変数にはreadLineプロシージャの返り値が代入されている。
このコードの場合、型の指定を宣言から省いてもよい。
というのも、コンパイラが「readLineプロシージャが文字列型を返す事」を把握しているからである。(これはローカル型推論と呼ばれる。)
よって以下の記述も可能である。
var name = readLine(stdin)
Nimにおける型推定が基本的に、このローカル型推定である事を覚えておいてほしい。これは簡潔さと可読性とが歩み寄った結果である。
これらの組み込まれた識別子は、システムモジュールで宣言されており、これは他の全てのモジュールが暗黙にインポートする。3
echo
・readLineといった、コンパイラが既に把握している識別子がこの"hello world"コードには含まれている。これらの組み込まれた識別子は、システムモジュールで宣言されており、これは他の全てのモジュールが暗黙にインポートする。3
字句要素(リテラルや識別子のこと) Lexical elements
Nimの字句要素を詳しく見てみよう。
他のプログラミング言語と同じくNimを構成する要素は、(文字列)リテラル、識別子、キーワード、コメント、演算子、その他記号である。
他のプログラミング言語と同じくNimを構成する要素は、(文字列)リテラル、識別子、キーワード、コメント、演算子、その他記号である。
文字列リテラル、文字リテラルString and character literals
文字列リテラルは二重引用符
特殊文字をエスケープするには
raw文字列リテラルもある。4
rawstring.nim
"
で囲まれる。一方の文字リテラルは単引用符'
で囲まれる。特殊文字をエスケープするには
\
を用いる。例えば、\n
は改行、\t
はタブを意味する。raw文字列リテラルもある。4
rawstring.nim
r"C:\program files\nim"
raw文字列リテラル中の
\
はエスケープ文字ではない。
文字列リテラルを記述する最後の方法は、long文字列リテラルという第三の方法である。
long文字列リテラルは3つの二重引用符に挟まれる。
複数行にわたって文字列を記述でき、
long文字列リテラルは3つの二重引用符に挟まれる。
""" ... """
複数行にわたって文字列を記述でき、
\
もエスケープ文字にならない。
使用例としては、HTMLコードを埋め込む、などがある。5
コメント Comments
文字列リテラル・文字リテラルの中でなければ、コメントは
ドキュメント・コメントは
#
で記述できる。ドキュメント・コメントは
##
が先頭に付く。# コメント
var myVariable: int ## ドキュメントコメント
ドキュメント・コメントは特別な存在である。
ドキュメント・コメントは抽象構文木6の中に含まれるため、
これにより、シンプルなドキュメンテーション・ツールで事足りる。
discard文8とlong文字列リテラルを組み合わせて使うことで、ブロックコメントを書く、という方法もある。
discard_and_longstring.nim9
ドキュメント・コメントは抽象構文木6の中に含まれるため、
.nim
ファイル中7の決まった場所にしか記述できないのだ。これにより、シンプルなドキュメンテーション・ツールで事足りる。
discard文8とlong文字列リテラルを組み合わせて使うことで、ブロックコメントを書く、という方法もある。
discard_and_longstring.nim9
discard """この中ならインデントの心配をすることなく
どんなコードもコメントアウトできる。
yes("つまらない質問をしてもよろしいですか?")"""
数値 Numbers
数値リテラルの書き方は、他の多くの言語と同じである。
また、アンダーバー
小数点
16進数は
また、アンダーバー
_
を使って可読性を上げることもできる。(100万:1_000_000
10)小数点
.
やe
を含む数字は浮動小数点型になる。(10億:1.0e9
)16進数は
0x
、2進数は0b
、8進数は0o
がそれぞれ先頭に付く。先頭に0
を付けたとしても、8進数には出来ない。var文 The var statement
var文を使うことで、ローカル変数やグローバル変数を新しく宣言できる。
var x, y: int # xとyが``int``型であることの宣言
var
文の後にインデントを入れると、ひとまとまりの変数を並べて宣言できる。var
x, y: int
# ここにコメントを入れることも可能である
a, b, c: string
代入文 The assignment statement
代入文を使うことで、新しく値を変数やメモリ領域に代入できる。
var x = "abc" # 新しい変数`x`を宣言し、値を代入する
x = "xyz" # `x`に新たな値を代入する
=
は代入演算子である。現在、代入演算子をオーバーロード(拡張)・上書き・禁止することは出来ないが、今後のバージョンで変わりうる。
複数の変数をまとめて宣言する際、代入演算子を用いると、全ての変数に同じ値を代入できる。11
assign_at_once.nim
var x, y = 3 # 3を変数`x`と変数`y`に代入
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"を出力
複数の変数をまとめて宣言し代入するときに、プロシージャが呼び出されると、思わぬ動作をすることは覚えておいてほしい。12
コンパイルするときに、宣言・代入文は解きほぐされ、結果としてプロシージャを複数回呼び出すことになるからだ。
もし、返り値が副作用にプロシージャに左右される物であれば、変数に別の値が入ってしまうかもしれない。
まとめて代入する時は、この点に気を付けよう。13
コンパイルするときに、宣言・代入文は解きほぐされ、結果としてプロシージャを複数回呼び出すことになるからだ。
もし、返り値が副作用にプロシージャに左右される物であれば、変数に別の値が入ってしまうかもしれない。
まとめて代入する時は、この点に気を付けよう。13
定数 Constants
const x = "abc" # 定数xは文字列"abc"を含む
const
の後にインデントを入れると、ひとまとまりの定数を並べて定義できる。const
x = 1
# ここにもコメントが書ける
y = 2
z = y + 5 # 計算が可能だ
let文 The let statement
let
文はvar
文のような働きを持つが、単一代入変数の宣言に用いられる。つまり、初期化後、改めて値を代入できないのだ。
let.nim(注:コンパイルエラーを吐きます)
let x = "abc" # 新たな変数`x`を宣言し、値と結びつける
x = "xyz" # 不正な操作: `x`への代入
let
文とconst
文の違いは以下のとおりである。let
とは再代入できない変数である。const
とは"コンパイル時に強制的に計算され、データ領域に代入しておく"
const_readLine.nim(注:コンパイルエラーを吐きます)
const input = readLine(stdin) # エラー:定まった値を返す式が必要
let input = readLine(stdin) # 実行できる
制御文 Control flow statements
最初の"greetings.nim"プログラムは、全てが順番に実行される3つの文から構成される。
これで上手く作れるのは、簡単なアルゴリズムだけである。
要するに、分岐や繰り返しも必要だ。
これで上手く作れるのは、簡単なアルゴリズムだけである。
要するに、分岐や繰り返しも必要だ。
if文 If statement
if文はプログラムを分岐させる一つの方法である。
greeings_if.nim(注:stdinに
""
を入力したい場合は、一回改行をしておかないと、runtimeエラーを吐きます)let name = readLine(stdin)
if name == "":
echo "憐れな。名を失ったのか?"
elif name == "名前":
echo "面白いね、君の名前は名前なのかい。"
else:
echo "やあ、", name, "!"
if
文では0個以上のelif
を入れる事ができ、else
は任意である。elif
というキーワードはelse if
の略であり、過剰なインデントを回避するために使う。(コード中の
""
は空の文字列を示す。文字を含まない)case文16 Case statement
プログラムを分岐させるもう一つの方法はcase文を使う。case文は複数に分岐できる。
let name = readLine(stdin)
case name
of "":
echo "憐れな。名を失ったのか?"
of "name":
echo "面白いね、君の名前は名前なのかい。"
of "デイブ", "フランク":
echo "かっこいい名前じゃないか。"
else:
echo "やあ、", name, "!"
見てわかる通り、
case文は、整数型、他の順序型や文字列型を受けることが出来る。(順序型の説明は少し後にする)
整数型やほかの順序型に関しては、範囲指定も可能である。
of
の後に、コンマ区切りで値を並べることも可能である。17case文は、整数型、他の順序型や文字列型を受けることが出来る。(順序型の説明は少し後にする)
整数型やほかの順序型に関しては、範囲指定も可能である。
#真下のこの文は後で説明する
from strutils import parseInt
echo "数字を入力してください: "
let n = parseInt(readLine(stdin))
case n
of 0..2, 4..7: echo "入力した数字は以下の集合に属しています: {0, 1, 2, 4, 5, 6, 7}"
of 3, 8: echo "入力した数字は3か8です"
しかし実は上のコードはコンパイル出来ない。
それは、nが取りうるすべての値を網羅する必要があるのに、このコードでは
「取りうる全ての整数を書く」事はあまり実用的でない(範囲指定の記法で可能ではあるが)。そのため、このコンパイルエラーを直すには、他の値に関しては特に実行することが無い事をコンパイラーに伝えるのがよい。
それは、nが取りうるすべての値を網羅する必要があるのに、このコードでは
0..8
の範囲の値しか扱っていないからである。「取りうる全ての整数を書く」事はあまり実用的でない(範囲指定の記法で可能ではあるが)。そのため、このコンパイルエラーを直すには、他の値に関しては特に実行することが無い事をコンパイラーに伝えるのがよい。
...
case n
of 0..2, 4..7: echo "入力した数字は以下の集合に属しています: {0, 1, 2, 4, 5, 6, 7}"
of 3, 8: echo "入力した数字は3か8です"
else: discard
単独のdiscard文8は何も実行するな文である。
elseの付いたcase文がエラーを持たない事をコンパイラは知っており、このエラーは消えるのである。
全ての文字列型を網羅することは不可能である事は覚えておいてほしい。というのも、網羅できない事が「case文を文字列で分岐するときは
elseの付いたcase文がエラーを持たない事をコンパイラは知っており、このエラーは消えるのである。
全ての文字列型を網羅することは不可能である事は覚えておいてほしい。というのも、網羅できない事が「case文を文字列で分岐するときは
else
が必要」な理由だからである。
一般的に、case文は部分範囲型や列挙型で使われる。これらの型の場合、取りうる値を尽くしたかのコンパイル時の確認が役に立つからである。
while文 While statement
while文は簡単なループ構造である。
echo "お名前は? "
var name = readLine(stdin)
while name == "":
echo "名前を教えてください "
name = readLine(stdin)
# 新しい変数を宣言している訳ではないため、ここに``var``はない
コード例では、ユーザーが何も入力しない場合(Returnばかり押す)、入力するまで名前を聞くためにwhileループを使っている。
for文 For statement
echo "10まで数える: "
for i in countup(1, 10):
echo $i
# --> 1 2 3 4 5 6 7 8 9 10 をそれぞれ別の行に出力する。
組み込み演算子である
変数
それぞれの
以下のコードも同じ操作をする。
$
は整数型(int
)や他のデータ型を文字列型に変換する。変数
i
はforループにより暗黙に宣言されており、countupがint
型を返すため、i
も同じ型を持つ。i
は1,2,...,10と変わっていく。それぞれの
i
の値は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
# --> Outputs 10 9 8 7 6 5 4 3 2 1 on different lines
プログラムでカウントアップする事は、かなりよくある事であり、Nimは
..
という同じ動作をするイテレータを持つ。for i in 1..10:
...
スコープとblock文 Scopes and the block statement
制御文について、まだ話していないことがある。
それは「新しいスコープを作る」ということだ。
この事は要するに、以下の例では
それは「新しいスコープを作る」ということだ。
この事は要するに、以下の例では
x
はループ外で取得できない、という意味だ。
block_scope.nim(注:コンパイルエラーを吐きます)
while false:
var x = "やあ"
echo x # 実行できない
while文やfor文は暗黙にブロックを作ります。
識別子は、それが宣言されたブロックからしか見えません。
識別子は、それが宣言されたブロックからしか見えません。
block
文は明示的にブロックを作るのに使われます。
block.nim(注:コンパイルエラーを吐きます)
block myblock:
var x = "hi"
echo x # こちらも動かない
ブロックのラベル(コード例においては
myblock
)は任意である。break文 Break statement
break
文を使うとブロックを途中で抜けることができる。break
文が抜けることができるブロックはwhile
、for
、block
文によるブロックである。break
文により、最も内側のブロックを抜ける事が出来るが、抜けるブロックをラベルで指定出来る。block myblock:
echo "ブロックに入る"
while true:
echo "ループの中"
break # ループからは抜ける、ブロックからは抜けない
echo "まだループの中"
block myblock2:
echo "ブロックに入る"
while true:
echo "ループの中"
break myblock2 # ブロックを抜ける(ループからも)
echo "まだループの中"
continue文 Continue statement
他のプログラミング言語と同様、
continue
文により、直ちに次の反復処理に移る事が出来る。
continue.nim (注:runtimeエラーを吐きます19)
while true:
let x = readLine(stdin)
if x == "": continue
echo x
when文 When statement
コード例:
when system.hostOS == "windows":
echo "Windowsで動かしていますね!"
elif system.hostOS == "linux":
echo "Linuxで動かしていますね!"
elif system.hostOS == "macosx":
echo "Mac OS Xで動かしていますね!"
else:
echo "私の知らないオペレーション・システムです"
when
文はif
文とほとんど同じだが、いくつかの違いを持つ。- 書かれる条件式は全て、コンパイラが計算できるように定まった値を持たなくてはならない。
- 分岐内の文は新しいスコープを持たない。
- コンパイラは、構文を解析し、コンパイル時点で
true
だった最初の分岐の中の式のみをコンパイルする。when
文は各プラットフォームに適したコードをそれぞれ書くときに便利であり、C言語における#ifdef
文と似ている。
一口メモ:コメントアウトするコードが大きい場合、普通のコメントを使うよりも
when false:
を使った方が良い事がよくある。この方法なら、ネストが可能だからだ。文とインデント Statements and indentation
基本的な制御文を押さえたところで、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文中の条件式が挙げられる。
式の中にインデントを入れることで、可読性を高めることも可能である。
例として、if文中の条件式が挙げられる。
式の中にインデントを入れることで、可読性を高めることも可能である。
indented_expression.nim(注:コンパイルエラーを吐きます)23
if thisIsaLongCondition() and
thisIsAnotherLongCondition(1,
2, 3, 4):
x = true
経験則として、式中のインデントが良いのは演算子・閉じていない括弧・コンマの後である。
括弧とセミコロン
(;)
を使うことで、式しか許されない場所でも文が使える。# fac(4)、つまり4の階乗をコンパイル中に計算する
const fac4 = (var x = 1; for i in 1..4: x *= i; x)
プロシージャ Procudures
これまでのコード例で出てきた、echo、readLineのような新しい命令を定義するには、プロシージャという概念が必要となる。(メソッドや関数と呼ぶ言語もある24)
Nimで新しいプロシージャを定義するには、
Nimで新しいプロシージャを定義するには、
proc
というキーワードを使う。proc yes(question: string): bool =
echo question, " (はい/いいえ)"
while true:
case readLine(stdin)
of "はい", "そうです", "可", "〇": return true
of "いいえ", "ちがいます", "不可", "×": return false
else: echo "「はい」か「いいえ」で明確に答えてください"
if yes("全ての君の重要なファイルを消そうか?"):
echo "デイブ、申し訳ない。私には出来ない。"
else:
echo "君も私と同じくらい、なにが問題かわかっているようだね。"
この例では、
コード例中の
また、if文やwhile文などの条件式はブーリアン型でなくてはいけない。
yes
という名前のプロシージャが登場する。yes
はquestion
に格納される質問をユーザーにした上で、「はい」やそれに似た返事に対してはtrueを、「いいえ」や似た返事に対してはfalseを返り値として返す。return
文があると、直ちにプロシージャから抜ける。(従って、whileループも抜ける)コード例中の
(question :string): bool
という構文は、「question
というstring
型のパラメータがあり、bool
型の値を返すこと」を示す。bool
型(ブーリアン型)の話をすると、これは組み込みのデータ型であり、true
とfalse
しか有効な値を持たない。また、if文やwhile文などの条件式はブーリアン型でなくてはいけない。
result変数 Result variable
返り値を返すプロシージャでは、返り値を表す
式が後に続いていない
出口の
result.nim
result
が暗黙に宣言されている。26式が後に続いていない
return
文はreturn result
の省略形である。出口の
return
が存在しないプロシージャは、終わる際に必ず、自動的にresult
の値を返す。result.nim
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
変数を同じ名前の普通の変数で覆い隠し、本来のresult
変数が持つ機能を失う事を意味する。27result変数はプロシージャの返す型のデフォルト値で初期化28されている。
参照型のresult変数は、プロシージャが宣言されたときは
nil
の値をとるため、状況に応じて、手動で初期化をしなくてはならない。パラメータ Parameters
プロシージャの中身に記述されたパラメータは、定数として扱われる。
つまり、何も変更しないと、パラメータの値は変えられないのだ。
というのもその方が、より効率的にコンパイラによるパラメータの受け渡しが出来るからである。
プロシージャ内でミュータブルな変数を使いたければ、プロシージャ内で
パラメータ名は上書き可能であり、それどころか、実は慣用的に用いられる。
つまり、何も変更しないと、パラメータの値は変えられないのだ。
というのもその方が、より効率的にコンパイラによるパラメータの受け渡しが出来るからである。
プロシージャ内でミュータブルな変数を使いたければ、プロシージャ内で
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 # 整数型のmod計算
var
x, y: int
divmod(8, 5, x, y) # xとyを変更する
echo x
echo y
コード例の中の
varパラメータはプロシージャで変更することが可能であり、呼び出し先にも引数の変更が伝わっている。31
上の例では、varパラメータを使うよりも、タプルを返した方が適切であることは覚えておいてほしい。32
res
とremainder
がvarパラメータである。varパラメータはプロシージャで変更することが可能であり、呼び出し先にも引数の変更が伝わっている。31
上の例では、varパラメータを使うよりも、タプルを返した方が適切であることは覚えておいてほしい。32
discard文 Discard statement
副作用として返り値を持つプロシージャを呼び出しつつ、その返り値を無視するには、
discard
文が絶対に必要である。
discard_result.nim(注:
yes
プロシージャも定義してあります。)discard yes("つまらない質問をしてもよろしいですか?")
呼び出したプロシージャやイテレータを宣言した際に
discardable
プラグマが使われていた場合、discard
で明示的に示さずとも、返り値を無視できる。proc p(x, y: int): int {.discardable.} =
return x + y
p(3, 4) # 今は可能
コメントの項目で開設した通り、
discard
文はブロックコメントを作る時にも使える。名前付き引数 Named arguments
プロシージャが多くのパラメータを持つ事はよくあり、その場合、パラメータの順番が分からなくなる。
これは特に、複雑なデータ型を形成するプロシージャでよく起こる。
従って、どの引数がどのパラメータに値を渡すのかを明確にするために、プロシージャに渡す引数は名前を付けることができる。
これは特に、複雑なデータ型を形成するプロシージャでよく起こる。
従って、どの引数がどのパラメータに値を渡すのかを明確にするために、プロシージャに渡す引数は名前を付けることができる。
create_window.nim(注:コンパイルエラーを吐きます33)
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個になる事はコンパイラによって確かめられる。
デフォルト値 Default values
createWindow
プロシージャを使いやすくするには、デフォルト値を持たせればいい。呼び出し時に指定を受けない限り、このデフォルト値が引数として使われる。
default_value.nim(注:コンパイルエラーを吐きます)33
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
はデフォルト値と違う引数だけを指定すればよくなった。デフォルト値を使って、パラメータの型推論が行われることを覚えておいてほしい。つまり例えば、
titel: string = "unknown"
と書く必要はない。プロシージャのオーバーロード Overloaded procedures
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)プロシージャを呼び出す
(
どのプロシージャが呼び出された
このオーバーロードを解決するアルゴリズムに関しては、ここではなくマニュアルで詳しく扱う。
とはいえ、思いつきもしない嫌なアルゴリズムではなく、比較的簡単な単一化のためのアルゴリズムに基づいている。
曖昧なプロシージャ呼び出しに対しては、エラーが返される。
toString
が普通はNimの$演算子で書かれることを覚えておいてほしい)どのプロシージャが呼び出された
toString
として適切かは、コンパイラが選ぶ。このオーバーロードを解決するアルゴリズムに関しては、ここではなくマニュアルで詳しく扱う。
とはいえ、思いつきもしない嫌なアルゴリズムではなく、比較的簡単な単一化のためのアルゴリズムに基づいている。
曖昧なプロシージャ呼び出しに対しては、エラーが返される。
演算子 Operators
Nimのライブラリでは、オーバーロードが非常によく使われる。
一つの理由としては挙げられるのは、
中置表記
中置表記の演算子は引数を必ず2つ取り、前置表記の演算子は必ず一つ取る。
後置表記があると、曖昧になるため、後置演算子は出来ない。
曖昧になるというのは例えば、
Nimでは後置表記をなくす事で、これは必ず
一つの理由としては挙げられるのは、
+
のような演算子それぞれが、まさにオーバーロードされたプロシージャである事だ。36中置表記
a + b
も前置表記も+ a
パーサーで解釈できる。中置表記の演算子は引数を必ず2つ取り、前置表記の演算子は必ず一つ取る。
後置表記があると、曖昧になるため、後置演算子は出来ない。
曖昧になるというのは例えば、
a @ @ b
が(a) @ (@b)
と(a@) @ (b)
のどちらを意味するのかが分からない、ということだ。Nimでは後置表記をなくす事で、これは必ず
(a) @ (@b)
である事、が言えるようになったのである。and
、or
、not
などの、ほんのいくつかの組み込みの演算子を除き、演算子は以下の記号から構成される+ - * \ / < > = @ $ ~ & % ! ? ^ . |
。ユーザー定義の演算子は作れる。
@!?+~
という独自の演算子を定義することも可能だが、こんな演算子を定義した場合は可読性が損なわれるかもしれない。37
演算子の計算の優先順序は、演算子の最初の文字で決まる。マニュアルに詳しい説明がある。
新しい演算子を定義するには、演算子の記号をバックティック
` `
で挟めばいい。38proc `$` (x: myDataType): string = ...
# オーバーロードの問題は解決できるため、今後は$演算子をmyDataTypeでも使える
# $演算子を他の演算子に対して使っても、作動することは保証されている
他のプロシージャを呼び出す時のように演算子を呼び出したい時も、
` `
記法が使える。if `==`( `+`(3, 4), 7): echo "True"
前方宣言 Forward declarations
全ての変数やプロシージャは、使う時点で宣言されている必要がある。
(というのも、Nimと同じくらい広くメタプログラミングが使えるプログラミング言語では同程度以上の事が必要になるのである)42
しかし、相互再帰なプロシージャを使う場合、このルールに従うことが出来ない。
forward_declaration.nim43(注:以下の両コードを含みます)
(というのも、Nimと同じくらい広くメタプログラミングが使えるプログラミング言語では同程度以上の事が必要になるのである)42
しかし、相互再帰なプロシージャを使う場合、このルールに従うことが出来ない。
forward_declaration.nim43(注:以下の両コードを含みます)
# 前方宣言
proc even(n: int): bool
proc odd(n: int): bool =
assert(n >= 0) # nが負数になり無限ループする事がない事を保証する
if n == 0: false
else:
n == 1 or even(n-1)
proc even(n: int): bool =
assert(n >= 0) # nが負数になり無限ループする事がない事を保証する
if n == 1: false
else:
n == 0 or odd(n-1)
コード例では、
したがって、
そのための構文は簡単であり、
odd
はeven
に依存し、逆もまたしかりである。したがって、
even
を完全に定義する前であっても、コンパイラにeven
の存在を知らせる必要があるのだ。そのための構文は簡単であり、
=
とプロシージャの中身を省略するだけでいい。assert
は境界条件を与えているに過ぎず、モジュールの項で触れる。
今後のNimのバージョンにおいては、前方宣言に要求されるものが減らされる。
コード例から分かるもう一つの事として、プロシージャの中身は単一の式だけでも十分だという事だ。
この場合、式の値が暗黙に返り値とされる。
この場合、式の値が暗黙に返り値とされる。
イテレータ以降の項を含む後編は鋭意翻訳中
訳注
- 恐らくRammsteinのMorgensternという歌からの引用。引用意図はぶっちゃけよく分からない。 ↩
- これはコンパイルの開発者に向けて書かれていると考える。というのも、Nimはいまだにver1.0に到達しておらず、(憶測ではあるが)コンパイル開発者もコミュニティに多くいるはずだからである。とはいえ、これが何を意味するかは明白である。例えば、.nim上では定数にプロシージャ経由で値を与えていても、.exe上ではその返り値がもう計算され、代入されているのである。さすコン。 ↩
- 元となった言語はよく分からないが、よくある
switch-case
に比べて英語の文法としては自然に見える。 ↩ break
がないのは、コンマ区切りが使えるからであろう。 ↩- ideoneのバージョンが古く(0.11.2)、何かと色々があると考えられる。インストールした環境(0.15.2)では問題なく動く。ideoneのコードでは
$i
を()
で挟んだ。パッと見た限り動作、出力に違いはない。 ↩ - どうやらideoneはlinuxを使っているようだ。(出力を見ながら) ↩
- この文を読む限り、Nimにはメソッド・プロシージャ・関数の間に厳密な区別を設けようと考えてはなさそうだ。この中の日本語訳は「プロシージャ」で統一したが、別の意見があればコメントで勝手に討論してほしい。 ↩
- 「暗黙に宣言されている」と言っても、実際には
proc hoge(): type =
の部分が宣言に相当している。現に、type
にどのデータ型を割り当てるかによって、resultの型も変わる。また、type =
を抜かすと「resultが宣言されていない」という趣旨のエラーが返ってくる。 ↩ - 初期値をイジれそうな方法はいくつか思いつく。私が思いついたのはブロック内で
result = initial_value
を書く、proc hoge() : type = initial_value
と宣言文で初期化する、パラメータ名をresultにするの3つである。どれが上手くいくかは、実際に確かめてみてほしい。また、他にうまくいった方法があれば、知らせてほしい。 ↩ - ここでは関数の宣言のみをしている。実際に挙動を確かめたい人は
printSeq(@[1,2,3], 2)
などと打ち込むとよいだろう。 ↩ Window
型が定義されていないため、致し方ない。domというライブラリの中に、Window
型がある。いろいろ調べてみたが、javascriptプラットフォームを使うくらいしか分からなかった。nim js createWindow.nim
を実行するとエラーなく実行できたが非常に長いファイルで、読む気が失せたため、Gistで共有しておく。というより、私のような初心者プログラマにわかるわけない。 ↩- エラーを回避するため、createWindowプロシージャの型は整数型にしてある。 ↩
- リンク先では
...
をdiscard
と置き換えたが、つまらないため、ローマ数字に置き換えるコードも書いてみた。あまり綺麗なコードではないので、参考にしない事。(アドバイスください)言うまでもないが、$
にローマ数字に置き換える機能などない。 ↩ - 私のWindowsPCの日本語キーボードでは
SHIFT+@
で打ち込める。英語キーボードの場合、1の左にあることが多い。 ↩ - コンパイルエラーを避けるために、
type myDataType = object
を先頭に書き加えている。 ↩ - 本当にどうでもいいことだが、Nimで真偽の真を意味するのはtrueではなく、Trueである。 ↩
- この文章は文法が入り組んでいるので、少し解説しておく。原文は"The reason for this is that it is non-trivial to do better than that in a language that supports meta programming as extensively as Nim does."となっている。まず、全体の構造は"The reason for A is B."(Aである理由は、Bである)である。ここでAは"this"(=前の文の「宣言されている必要がある」)、Bは"it is non-trivial ... Nim does"が対応する。次にBの構造は"it is C to D"(DすることはCだ)である。Cは"non-trivial"(=些細でない(=重要・必要なこと))であり、Dは"do better ... Nim does"となる。さらに、Dの構造を考えると"do better than E in F"(=Fにおいては、Eよりも良くする)となる。ここで「良くする」というのは"better"を訳したものであるが、betterの意味はかなり広く、この文においては「より丁寧に・より十分に」くらいが当てはまるだろう。さて、Dの構造の中のE、Fに話を戻すと、Eは"that"(=前の文の「宣言されている必要がある」)であり、Fは"a language that ... Nim does"である。Fは"a language"に"that"以下の"supports ... does"が掛かる構造をしており、"supports meta programming as extensively as Nim does"は「Nimと同じくらい広く、メタプログラミングを支援している」と直訳できる。あとは、この論理をまとめれば良いが、分かりにくいため意訳している。 ↩
0 件のコメント:
コメントを投稿