本題の前に…パイプライン演算子
前提として,こんな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);
}
記号的にも読みやすいと思います.パイプライン演算子を使うと可読性が上がります.
是非,活用してみるといいでしょう.(F#のパイプライン演算子と似たものです.)
List
まず,Nemerleには,Nemerle.Core.listがあります.
1 def l : list[int] = [0, 2];
2 def ll : List[int] = List();
3 l.GetType().ToString() |> WriteLine;
4 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]
直感でわかるんじゃないでしょうか
まず,[]は”,”でどんどん要素を定義していきます.
$[]は,次のような書式です.
1 $[first .. final]
2 $[<function>, <param_name> in <list>, <condition>]
1番目のものはわかりやすいですね.
問題は,2番目.
まずは,二項目,<list>から一項ずつ<param_name>で取り出します.
それから,取り出した<param_name>を<condition>で判断します.<condition>がfalseを返した場合,その値は破棄されます.
最後に,<function>で加工をします.この<function>,1行だけであれば,;
や{}
は要りません.
LINQで表現するとこんな感じでしょうか.
<list>.Where(<condition>).Select(<function>);
Nemerleは他の関数型言語に劣らず,二項目は,何個でも書くことができます.
def l = $[x * y, x in [1 .. 3], y in [5 .. 7]]
実際に実行してみるとわかると思います.(丸投げ)
リスト関連の様々なメソッド
先頭追加 ::
演算子::は,リストの先頭に値を追加します.
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のWhenみたいなもので引数の関数がtrueを返す値だけのリストを返します.
MapはLINQのSelectみたいなもので引数の関数でリストの値を整形します.
遅延評価?
LINQは遅延評価ですが,NemerleのListは正格評価も遅延評価もでき,"lazy"がついたメソッドを使えば遅延評価でできます.
foreach文
def l = [1,2,3];
foreach(n in l) {
WriteLine(n);
}
一般的なforeach文です.
1 def l = [1,2,3];
2 foreach(n is int in l) {
3 WriteLine(n);
4 }otherwise{
5 WriteLine("There are no elements.");
6 }
2行め,is
で型指定ができ,その型の値のみをリストから取り出すことができます.
is
ではなく,:>
にすると,リストにその型に合わない値が含まれていた場合,例外を投げます.
4行目~,もし,一回もforeach内のコードが実行されなかった場合,otherwiseの中のコードが実行されます.
パターンマッチ
パターンマッチとは,簡単に言ってしまうとSwitch文ですが,C#やC++のswitch文より非常に強力です.
書式は次のとおりです.
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つは,リストの部分マッチです._
は,どんな値でもおkと言う意味です.
[ ]
で,パターンマッチすると,要素数が限定されてしまいます.上の例では,パターンの[]
内に,2つの要素があるので,長さが2のリストだけにマッチします.
::
で,パターンマッチすると,要素数は限定されませんが,::
の右側の要素にはリストの値しかいれることができません.従って,22 :: _ :: 12
というようなことはできません.
上から6つ目の[]
はからのリストとマッチします.
head::tail
match(some_list) {
| x :: xs =>
WriteLine($"Head is $x ");
WriteLine($"Tail is $xs ");
| [] => do_something();
}
Haskellに触れたことがある人は,x:xs
はお馴染み深いものでしょう.(Nemerleは::
ですが)
このように,変数A :: 変数B
のパターンの時は,どんなリストにもマッチし,変数A
には,リストの先頭が,変数B
には,リストの先頭を除いた全体が代入され,使えるようになります.
少し前にforeach文を紹介しましたが,それと同じようなことが,match文でできます.
foreachの場合
main() : void {
def l = $[1 .. 5];
mutable n = 0;
foreach(i in l) {
n += i;
}
WriteLine(n);
}
x::xsパターンの場合
AddFunc(n : int, l : list[int]) : int {
match(l) {
| x :: xs => AddFunc(n + x, xs)
| [] => n
}
}
main() : void {
AddFunc(0, $[1 .. 5]) |> WriteLine;
}
個人的にx::xs
の方がカッコイイ気がします.
〆
今回は,関数型言語としてのNemerleをざっくり踏み込んでいきました.慣れが必要ですので,何回も挑戦することが大事であると思います.(といっても,私もまだマスターしていません)
入門Ⅳへ