本題の前に…パイプライン演算子
F#のパイプライン演算子と似たものがNemerleにもあります.
3つの関数があるとします.
MethodA(n : int) : int {
n + 2
}
MethodB(n : int) : int {
n * 2
}
MethodC(n : int) : int {
System.Math.Pow(n, 2)
}
MethodAから順番に次のように実行します.
main() : void {
mutable n = 0;
n = MethodA(n);
n = MethodB(n);
n = MethodC(n);
}
これを,一行にまとめることもできます.
main() : void {
mutable n = 0;
n = MethodC( MethodB( MethodA( n ) ) );
}
しかし,これは非常に見づらいです.そんな時にパイプライン演算子を使います.
main() : void {
mutable n = 0;
n = (n |> MethodA |> MethodB |> MethodC);
}
読みやすくなったかと思います.パイプライン演算子を使うと可読性が上がります.
List
まず,Nemerleには,Nemerle.Core.list
があります.
def l : list[int] = [0, 2];
def ll : List[int] = List();
l.GetType().ToString() |> WriteLine;
ll.GetType().ToString() |> WriteLine;
上の例では,1行目にNemerleの構文で作ったリスト,2行目に.Net標準のクラスSystem.Collections.Generic.List
を作ってます.
これを実行すると,
Nemerle.Core.list`1+Cons[System.Int32]
System.Collections.Generic.List`1[System.Int32]
このように違います.本記事では,Nemerle.Core.list
の方で説明していきます.
リストをつくる
1 def emptyList = []; // result: []
2 def allwritedList = [0, 1, 2, 3]; // result: [0, 1, 2, 3]
3 def rangeList = $[0 .. 3]; //result; [0, 1, 2, 3]
4 def rangeListWithStep = $[1, 3 .. 10]; //result: [1, 3, 5, 7, 9]
5 def rangeListWithFunction = $[i * i, i in [1 .. 5]]; //result: [1, 4, 9, 16, 25]
6 def rangeListWithCondition = $[i, i in [1 .. 5], i % 2 == 0]; //result: [2, 4]
リストの生成には,C#のリスト生成記法に似た方法で記述ができます.
また,PythonやF#にもあるようなZF式でも生成ができます.
リスト関連の様々なメソッド
先頭追加 ::
演算子::
は,リストの先頭に値を追加します.
def A = 2 :: [];
// A is [2]
def B = 3 :: A;
// B is [3, 2];
Head,Tail,Last
Head
はリストの先頭,Tail
はリストから先頭を除いた全体(要するにリストの2個目以降)を返します.
Lispでいう,car
,cdr
みたいなものですね.
Last
は名前のとおりです.
Filter,Map
def a = $[1 .. 5];
WriteLine(a.Filter(fun(i) { i % 2 }));
//result: [2, 4]
WriteLine(a.Map(fun(i) { i * 2 }));
//result: [2, 4, 6, 8, 10]
Filter
はLINQのWhere
みたいなもので引数の関数がtrue
を返す値だけのリストを返します.
Map
はLINQのSelect
みたいなもので引数の関数をリストの各要素に適用します.
遅延評価リスト
LINQは遅延評価ですが,NemerleのList
は正格評価も遅延評価もでき,lazy
がついたメソッドを使えば遅延評価でできます.
foreach文
def l = [1,2,3];
foreach(n in l) {
WriteLine(n);
}
一般的なforeach文です.
def l = [1,2,3];
foreach(n is int in l) {
WriteLine(n);
} otherwise {
WriteLine("There are no elements.");
}
2行目,is
で型指定ができ,その型の値のみをリストから取り出すことができます.
is
ではなく,:>
にすると,リストにその型に合わない値が含まれていた場合,例外を投げます.
4行目~,もし,一回もforeach内のコードが実行されなかった場合,otherwiseの中のコードが実行されます.
パターンマッチ
書式は次の通りです.
match (some_value) {
| pattern1 => code1
| pattern2 => code2
...
}
some_valueにパターンマッチングする変数を,patternにパターンマッチングを,codeに,処理を書きます.
patternを_
にすると,他のパターンでマッチしなかった場合に実行されます.(いわゆるdefault
文)
C#と比較したコードを以下に示します.
int n = 1;
switch(n) {
case 1:
Console.WriteLine("One");
break;
case 2:
Console.WriteLine("Two");
break;
default:
Console.WriteLine("n isn't 1 or 2.");
break;
}
def n = 1;
match(n) {
| 1 => WriteLine("One");
| 2 => WriteLine("Two");
| _ => WriteLine("n isn't 1 or 2");
}
わかりやすいと思います.
次に,Listのパターンマッチングについて.
match (some_list) {
| [42, 42] => "two forty-two"
| [42, _] => "forty-two on first position of two-element list"
| [_, 42] => "forty-two on second position of two-element list"
| 42 :: _ => "forty-two on first position"
| _ :: 42 :: _ => "forty-two on second position"
| [] => "an empty list!"
| _ => "another list"
}
上から5つは,リストの部分マッチです._
は,任意の値という意味です.
[ ]
で,パターンマッチすると,要素数が限定されてしまいます.上の例では,パターンの[]
内に,2つの要素があるので,長さが2のリストだけにマッチします.
::
で,パターンマッチすると,要素数は限定されませんが,::
の右側の要素にはリストの値しかいれることができません.従って,22 :: _ :: 12
というようなことはできません.
上から6つ目の[]
は空のリストとマッチします.
head::tail
match(some_list) {
| x :: xs =>
WriteLine($"Head is $x ");
WriteLine($"Tail is $xs ");
| [] => do_something();
}
このように,変数A :: 変数B
のパターンの時は,どんなリストにもマッチし,変数A
には,リストの先頭が,変数B
には,リストの先頭を除いた全体が代入され,使えるようになります.
〆
入門Ⅳへ