Tuple
C# 7でやっと使いやすくなったTuple
ですが,Nemerleは以前より使いやすいです.
Listと同じく,Nemerle独自のTuple
型(Nemerle.Builtin.Tuple
)で説明していきます.
NemerleのTuple
は標準で構造体です.
要素へのアクセスは,Item1, Item2 ...
ではなく,インデクサでアクセスします.
def two_pair = (1,2);
WriteLine($"first=$(two_pair[0]), second=$(two_pair[1])");
Nemerleのタプルは,個々のデータは変更できません.readonly
になっています.
mutable two_pair = (1,2);
two_pair[0] = 2; // Compilation Error!
関数に引数として渡すときにタプルを渡すと自動的に分解されます.
def Add(x : int, y) { x + y };
def tup = (1, 2);
Add(tup) |> WriteLine;
Variant
バリアントは直和型です.VisualBasicのVariantとは全く違います.
まずは基本形.ディスプレイサイズに関係するEnumを作ろうとします.
variant DisplaySize {
| VGA
| HD
| FullHD
}
まんまEnumです.ですが,画面のサイズの種類は多く,これだけでは表せません.そこで,Otherを追加します.
variant DisplaySize {
| VGA
| HD
| FullHD
| Other
}
しかし,Otherの時は,縦横のサイズを詳しく取得できません.そんな時に,バリアントが役立ちます.Other
のときだけ,Height
とWidth
という値を格納することできます.
variant DisplaySize {
| VGA
| HD
| FullHD
| Other {
Height : uint;
Width : uint;
}
}
バリアントは実際に,以下のように使います.
def MainDisplay = DisplaySize.HD();
def SubDisplay = DisplaySize.Other(800, 1280);
IL的に,バリアントは次のような構造になります.
class DisplaySize {
class VGA : DisplaySize {}
class HD : DisplaySize {}
class FullHD : DisplaySize {}
class Other : DisplaySize {
uint Height;
uint Wdith;
DisplaySize(uint _height, uint _width) {
Height = _height;
Width = _width;
}
}
}
もっとパターンマッチ
実践編などで,「もっともっとパターンマッチ」を入れようと考えていますが,今回は,Nemerleのパターンマッチの機能の全貌を紹介します.
tuple
基本的にはlist
と同じようにします.長さの違うタプルは駄目です.
def t = (1, 2);
match(t) {
| (1, _) => //Do Something
| (1, 2) => //Do Something
| _ => //Do Something if there aren't any matched condition.
| (1, 2, 3) => // Compilation ERROR
}
バリアント
さっきのDisplaySize
があるとしてパターンマッチングします.
def s = DisplaySize.FullHD();
match(s) {
| DisplaySize.VGA => //Do Something
| DisplaySize.HD => //Do Something
| DisplaySize.FullHD => //Do Something
| DisplaySize.Other(Height = h, Width = w) =>
WriteLine($"Your display size is h:$h w:$w");
| DisplaySize.Other(_, 1280) as ss =>
WriteLine(ss.Height);
値を持たない時は,そのまま,値を持つときは,上の例のように取り出すことができます.
其の時に,_
で何でも許容することもできます.as ss
はこの後紹介します.
型パターン/型スイッチ
Scala / C# 7.1のように,型パターン / 型スイッチすることができます.
def o : object = 12;
match (o) {
| i is int => WriteLine($"integer:$(i + 12)");
| str is string => WriteLine("text:" + str);
| _ => WriteLine("Unknown!");
}
object o = 12;
switch(o) {
case int i: WriteLine($"integer:{i + 12}"); break;
case string str: WriteLine("text:" + str); break;
default: WriteLine("Unknown!"); break;
}
val o : object = 12
o match {
case i : int => println(s"integer:${i + 12}")
case str : string => println("text:" + str)
case _ => println("Unknown!")
}
レコードパターン
何重にもif文を重ねなくてもいいようになります.
class hoge {
public hogeint : int;
public hogestr : string;
}
def hogeMatch(h : hoge) {
match(hoge) {
| (hogeint = 1, hogestr = "HOGEHOGE") => //DoSomething
| (hogeint = 2, hogestr = "HAGEHAGE") => //DOSomething
| _ => //DoElse
}
}
as
as <変数名>
で,パターンマッチングした変数を<変数名>
で扱うことができます.
def someFunc() { (12, 2) }
match(someFunc()) {
| (_, 2) as t => WriteLine($"$(t[0])";
| _ => //DoSomething
}
変数の定義文が減って,コードの視認性が上がると思います.
x::xsでの型指定
match(["aaa", "bbb"]) {
| (x : string) :: xs => //DoSomething
}
こんなこともできます.
複数条件
match (my_list) {
| [1]
| [1, 2] => WriteLine ("one or one, two");
| [x]
| [7, x] => WriteLine ($ "$x or seven, $x");
| _ => //Else
}
以下の様なことはできません.(x
が定義されない場合があるため)
| [1]
| [1, x] => WriteLine($"one, $x");
match
の省略
以下の様な場合,matchを省略できます.
def some(n : list[int]) {
match(n) {
| 3 :: _ => //DoSomething
| [5, _] => //DoSomething
| _ => //DoSomething
}
}
↓
def some(_) {
| 3 :: _ => //DoSomething
| [5, _] => //DoSomething
| _ => //DoSomething
}
この場合,some
は引数にlist[int]
を取ることになります.
with
言葉で説明するより見たほうが早いでしょう.
def foo (_) {
| [x] with y = 3
| [x, y] => x * y
| _ => 42
}
//foo (3) = 9
//foo (3, 4) = 12
//foo (3, 4, 5) = 42
2行目で,もし,fooに長さ1のlistが渡された場合,2つ目の要素(3行目に定義されているy)を3として,3行目のコードに渡します.このコードは以下のコードと同じです.
def foo (_) {
| [x] => foo_background(x, 3)
| [x, y] => foo_background(x, y)
| _ => 42
}
def foo_go(x : int, y) {
x * y;
}