コメント
行の // から行末までと, /* から */ までの間はコメントとして扱われ,コンパイル時に破棄されます。
// 行末までコメントです。 /* 複数行にわたってコメントを記述できます。 */
/* */ は複数行をコメント化するのに便利ですが,コメント中に */ が含まれていると,そこでコメント終了とみなされてしまいます。複数行をコメント化する際には,エディタの機能を利用して // を利用した方が良いでしょう。
コメントにはソースコードを読む人に有用な情報を記述するようにします。例えば複雑なアルゴリズムを実装している場合,そのアルゴリズムの出展や,そのアルゴリズムを採用した理由などは,後にソースコードを読み返した際に役立ちます。
ドキュメント
/// で始まる行コメント,あるいは /** から */ で囲まれたブロックコメントはドキュメントコメントとしてコンパイラに特別に扱われ,コンパイル時にドキュメントを生成させることができます。ドキュメントコメントは XML タグでマークアップされた形式で記述します。
/// <summary>
/// 整数値 <paramref name="value" /> を受け取り, 1 大きい値を返します。
/// </summary>
/// <param name="value">任意の整数値。</param>
/// <returns>与えられた整数値より 1 大きい値。</returns>
/// <exception cref="System.OverflowException">値が整数の取りうる範囲を超えたとき。</exception>
Increment(value : int) : int
{
checked(value + 1);
}
コンパイラに /doc オプションを加えることにより,ソースコード中のドキュメントコメントが XML ファイルとしてまとめて出力されます。
ドキュメントコメントの詳細については『XML ドキュメント コメント (C# プログラミング ガイド)』を参照してください。リンクは C# 対象のものですが, Nemerle でも同様に利用できます。
関数
モジュール内において関数は以下のような形式で定義されます。
module Module
{
Add(x : int, y : int) : int
{
x + y;
}
}
関数内で最後に評価された値が関数の結果となります。ただし,関数が void を返す場合は,最後に評価された値は返されません。明示的に void 値 () を返すこともできます。
VoidFunction() : void
{
();
}
モジュール内で定義されるトップレベル関数以外に,関数内で定義される局所関数もあります。局所関数は def キーワードが必要になります。
Add(x : int, y : int) : int
{
def LocalAdd(x, y)
{
x + y;
}
LocalAdd(x, y);
}
トップレベル関数はパラメーターや返り値の型を明示する必要がありますが,局所関数は型推論が働くため,多くの場合に型を明示する必要はありません。もちろんトップレベル関数と同様に明示的に記述することも可能です。
// y と返り値の型を明示。 x は型推論に頼る。
def LocalAdd(x, y : int) : int
{
x + y;
}
変数
モジュール内において変数は以下のような形式で定義されます。トップレベルの変数は型推論が働くため型を明示する必要はありません。明示する場合は名前の後ろに型を注釈します。
module Module
{
// 型推論により int になる。
IntValue = 1;
// 型推論により double になる。
InferredDoubleValue = 1.0;
// 右辺は int だが左辺が double のため暗黙の型変換により double になる。
DoubleValue : double = 1;
}
関数内で変数を用いる場合は,局所関数と同様に def キーワードを付加します。
def x = 1; def y : double = 1;
def キーワードで定義された変数は値を変更することはできません。代わりに mutable キーワードを付けることで更新可能な変数を定義することができます。
def x = 1; //x = 2; // コンパイルエラー mutable y = 1; y = 2;
def で定義した値は変更をすることができませんが,再定義することが可能です。一方 mutable で変数の再定義を行うと,コンパイラにより再定義の警告がされます。再定義により,それ以前の定義を隠ぺいすることができます。
def x = 1; def x = "foo"; mutable y = 2; mutable y = "bar"; // warning
使用しない変数の名前
Nemerle は宣言しているにも関わらず参照されない変数があると警告を出します。意図して参照しない場合はアンダースコアで始まる変数名,もしくはアンダースコア 1 文字の名前 (ワイルドカード) を利用します。
例えば 3 つの引数を取り,最初の引数を返す次のような関数を考えます。
def fst(x, y, z)
{
x;
}
この場合 2 番目の引数 y と 3 番目の引数 z は,これらの値が利用されていないという警告を出します。これを防ぐために,利用されない変数名に _ を付加します。
def fst(x, _y, _z)
{
x;
}
あるいは,より簡潔に,ワイルドカードを使うこともできます。
def fst(x, _, _)
{
x;
}
関数のパラメーターだけではなく,局所変数や局所関数でも同様に変数名の先頭にアンダースコアを付けることで,それ以降に参照されない場合に警告を表示しないようにできます。
なお,アンダースコアで始まる 2 文字以上の変数は普通の変数として扱うことができますが,ワイルドカードは普通の変数としては利用できません。ワイルドカードを変数のように扱う特別な用法について「ラムダ式」の節で説明します。
def _unusedFunction() {}
def _unusedValue = 1;
ワイルドカードにする場合は def キーワードを付けません。これは単に右辺値を無視する場合に便利です。例えばプログラムの最後にユーザーが何らかのキーを入力したら終了するというアプリケーションでは,ユーザーの入力行為が重要なのであって,その中身は重要でありません。したがってその入力値は無視する必要があります。
System.Console.Write("Press any key to exit.");
_ = System.Console.ReadKey(true);
予約されたキーワードや記号は通常使うことができません。しかし変数名の先頭に @ を付けると,予約されたキーワードや記号であっても,変数名として使うことができます。
def @match = "foo"; def @+- = (1, -1);
文字列
ダブルクォテーションで囲まれた文字列は文字列リテラルとして扱われます。文字列中のバックスラッシュ[A]は次に続く文字をエスケープし,特別な文字として扱います。
_ = "The quick brown fox jumps over the lazy dog.";
通常文字列中に直接改行コードやバックスラッシュを含むことはできず,代わりにエスケープシーケンス \n や \\ を記述する必要がありますが, "..." の代わりに @"...",あるいは <#...#> という記法を用いると,これらの文字を直接利用できます (逐語的文字列)。
_ = @"C:\Users\Taro\Pictures\*.jpg"
_ = <#using System.Console;
WriteLine("Hello, world!");
#>;
これらの文字列リテラルの先頭に $ を付加した $"..." や $@"..." や $<#...#> という形式で,文字列中の式を展開できます (スプライス文字列)。文字列中に式を記述するには $( と ) で囲います。また,参照先が列挙可能である場合, ..$() として先頭に .. を付けると列挙します。単純に変数を参照する場合は () は省略できます。
def x = 1; System.Console.WriteLine($"$x + 1 = $(x + 1)"); // "1 + 1 = 2" def list = [1, 2, 3]; System.Console.WriteLine($"$list enumerates ..$list."); // [1, 2, 3] enumerates 1, 2, 3.
ラムダ式
Nemerle には 2 つのラムダ式の記法があります。 1 つは C# と同様の方法,もう 1 つは fun キーワードによる記法です。
def negate = x => -x; _ = negate(1); // -1 def add = (x, y) => x + y; _ = add(2, 3); // 5
def subtract = fun(x, y) { x - y; };
_ = subtract(4, 5); // -1
上の例ではラムダ式のパラメーターに名前を付けましたが,このような単純なケースではパラメーターに名前を省略する方法もあります。パラメーターを書かずに,式中のパラメーターを _ に置き換えるだけです。式中に現れた _ がパラメーターに順番に置き換えられます。例えば次のようになります。
def divide = _ / _; // (x, y) => x / y と同じ。 _ = divide(1.0, 2.0); // 0.5
この方法を使うと,部分適用が容易に記述できます。例えば上の divide 関数を利用して 2 で割る関数や逆数を求める関数が定義できます。
def half = divide(_, 2.0); _ = half(4.0); // 2.0 def reciprocal = divide(1.0, _); _ = reciprocal(4.0); // 0.25
ブロック
{ から } で囲まれた部分はブロックとして一塊の値を表します。ブロックは関数と同様に,ブロック内で最後に評価された値がそのブロックの結果になります。ブロックの外からはブロック内の変数や関数を参照できないため,処理を局所化するのに便利です。
// ニュートン法で √2 を求める。
def sqrt2 = {
def error = 1.0e-5;
def newton(x)
{
def y = x / 2.0 + 1.0 / x;
if (System.Math.Abs(y - x) < error) y else newton(y);
}
newton(1.0);
};
この例では,ブロックを抜けた後では局所変数 error や局所関数 newton を参照できません。
なお,ブロックの最後の式は文末のセミコロン (;) が省略可能です。また,ブロックを抜けた直後 (} の後ろ) のセミコロンも省略可能です。上の例では 7, 9, 10 行目のセミコロンが省略できます。
ブロックには任意の名前のラベルを付けることができます。ラベル付きブロック内でそのラベルに値を指定してやると,そのブロックを即座に抜けることができます。
def blockResult = return: {
return(1);
2;
}
System.Console.WriteLine(blockResult); // 1
応用例として,反復処理でループを抜ける際に利用できます。
パターンマッチ
関数や match 式でパターンマッチが行えます。
def fibonacci(_)
{
| 0 => 0;
| 1 => 1;
| n => fibonacci(n - 1) + fibonacci(n - 2);
}
パターンマッチについては『Nemerle のパターンマッチ』にまとめてありますのでご参照ください。
条件分岐
条件分岐は when もしくは if-else を使います。 if-else では else は省略できません。
when (x < 0)
{
System.Console.WriteLine("x is negative.");
}
if (x < 0)
{
System.Console.WriteLine("x is negative.");
}
else
{
System.Console.WriteLine("x is not negative.");
}
when が常に void を返すのに対して if-else はブロック内で評価された値を返します。したがって if で評価される値と else で評価される値の型が不一致の場合型エラーになります。
when の否定処理である unless も利用できます。
unless (exitCode == 0)
{
System.Console.Error.WriteLine("Program didn't exit normally.");
}
反復処理
for, foreach, while, do-while, repeat といった様々なループ処理が利用できます。
for (mutable i = 0; i < 10; i++)
{
System.Console.WriteLine(i);
}
foreach (i in values)
{
System.Console.WriteLine(i);
}
mutable i = 0;
while (i < 10)
{
i++;
}
mutable i = 0;
do
{
i++;
} while (i < 10);
repeat (5)
{
System.Console.WriteLine("foo");
}
do-while は while と違い,最低 1 回の実行が保証されます。ループが実行されない場合の処理を, otherwise 節を後ろにつなげる事で実行できます。これは for, foreach, while で使えます。
foreach (_ in [])
{
System.Console.WriteLine("Element found.");
}
otherwise
{
System.Console.WriteLine("No element found.");
}
foreach で変数の型を指定したい場合は変数の後ろに is または :> で型名を注釈します。
foreach (x is int in values)
{
System.Console.WriteLine(x);
}
foreach (x :> int in values)
{
System.Console.WriteLine(x);
}
is は指定した型の要素のみを抜き出して列挙するのに対し, :> は整合性のない型の要素が含まれる場合に例外が投げられます。
反復処理中にブロックを抜けるには,反復処理を含むブロックにラベルを付け,そのラベルの値を設定することで即座にブロックを抜けることができます。
break: {
for (mutable i = 0; i < 10; i++)
{
when (i == 5) { break(); }
}
}
例外処理
例外は,処理中の異常を,処理を呼び出した側に通知する仕組みです。例えば,関数のパラメーターに範囲外の値を渡された場合,処理が実装されていない場合,整数値を 0 で割った場合などが挙げられます。
処理の呼び出し側に例外の発生を通知するには, throw キーワードを使用して, Exception もしくはその派生クラスのインスタンスを投げます。
throw System.NotSupportedException();
例外は処理の異常を通知する仕組みですので,発生した例外は処理しなければプログラムは異常終了します。起こりうる例外があらかじめ分かっている場合, try-catch-finally で発生した例外の処理を行うことができます。 try 節内に例外が発生する可能性があるコードを記述し, catch 節ではパターンマッチ形式で例外が発生した場合の処理を記述し, finally 節で終了時の処理を記述します。 finally 節は例外発生の如何に依らずの必ず実行されます。 catch または finally のいずれか一方は省略可能です。
try
{
System.Console.WriteLine("try");
}
catch
{
| ex is SomeException => System.Console.Error.WriteLine(ex.Message);
| _ is AnotherException => System.Console.Error.WriteLine("AnotherException");
}
finally
{
System.Console.WriteLine("finally");
}
try-catch-finally は値を持ちます。したがって try 節の最後に評価された値と, catch 節で返される値の型を一致させる必要があります。 finally は終了時の処理なので結果には影響しません。
def x =
try
{
Some(100 / divisor);
}
catch
{
| _ is DivideByZeroException => None();
}
finally
{
System.Console.WriteLine("finally");
}
catch 節において捕捉した例外は, throw キーワード単体で再度投げることができます。
try
{
// ...
}
catch
{
| ex is Exception => {
System.Console.Error.WriteLine(ex.Message);
throw;
}
}
もしこの例で throw の代わりに throw ex として捕捉した例外を投げると,スタックトレースや内部例外情報が破棄されます。
脚注
- 環境によっては円記号になります。 [↩]