LoginSignup
1
1

More than 5 years have passed since last update.

Nemerle入門Ⅲ ~list,パターンマッチ~

Last updated at Posted at 2016-02-23

本題の前に…パイプライン演算子

前提として,こんな3つの関数があります.

pipeline
MethodA(n : int) : int {
  n + 2
}

MethodB(n : int) : int {
  n * 2
}

MethodC(n : int) : int {
  System.Math.Pow(n, 2)
}

MethodAから順番に次のように実行します.

pipeline2
main() : void {
  mutable n = 0;
  n = MethodA(n);
  n = MethodB(n);
  n = MethodC(n);
}

これを,一行にまとめることもできます.

pipeline3
main() : void {
  mutable n = 0;
  n = MethodC( MethodB( MethodA( n ) ) );
}

しかし,これは非常に見づらいです.そんな時にパイプライン演算子を使います.

pipeline4
main() : void {
  mutable n = 0;
  n = (n |> MethodA |> MethodB |> MethodC);
}

記号的にも読みやすいと思います.パイプライン演算子を使うと可読性が上がります.
是非,活用してみるといいでしょう.(F#のパイプライン演算子と似たものです.)

List

まず,Nemerleには,Nemerle.Core.listがあります.

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の方で説明していきます.

リストをつくる

make_lists
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で表現するとこんな感じでしょうか.

$[]_in_LINQ
<list>.Where(<condition>).Select(<function>);

Nemerleは他の関数型言語に劣らず,二項目は,何個でも書くことができます.

multi_list
def l = $[x * y, x in [1 .. 3], y in [5 .. 7]] 

実際に実行してみるとわかると思います.(丸投げ)

リスト関連の様々なメソッド

先頭追加 ::

演算子::は,リストの先頭に値を追加します.

AddList
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

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文

foreach
def l = [1,2,3];
foreach(n in l) {
  WriteLine(n);
}

一般的なforeach文です.

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文より非常に強力です.
書式は次のとおりです.

patternmatch
match (some_value) {
  | pattern1 => code1
  | pattern2 => code2
  ...
}

some_valueにパターンマッチングする変数を,patternにパターンマッチングを,codeに,処理を書きます.
patternを_にすると,他のパターンでマッチしなかった場合に実行されます.(いわゆる,default文)
C#と比較したコードを以下に示します.

switch
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;
}
match
def n = 1;
match(n) {
  | 1 => WriteLine("One");
  | 2 => WriteLine("Two");
  | _ => WriteLine("n isn't 1 or 2");
}

わかりやすいと思います.
次に,Listのパターンマッチングについて.

ListPatternMatch
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

xxs_patternmatch
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の場合

Add_func
main() : void {
  def l = $[1 .. 5];
  mutable n = 0;
  foreach(i in l) {
    n += i;
  }
  WriteLine(n);
}

x::xsパターンの場合

Add_func
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をざっくり踏み込んでいきました.慣れが必要ですので,何回も挑戦することが大事であると思います.(といっても,私もまだマスターしていません)

入門Ⅳ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1