国民の祝日.csv の解析プログラムのScala版を頑張って作ってみた。

### はじめに
国民の祝日.csv が一ヶ月ほど前に[話題](http://qiita.com/jnchito/items/b8a2ed3544c1dc36fb9d)になりました。
ここ2年ほど趣味で学習しているScalaで変換にチャレンジしてみました。
『ScalaがXXXにより優れている』とかの意図はまったくありません。プログラミング言語による実装の違いが純粋に面白かったので、この記事を書いてみました。
![2017-03-2616-56-09.png](https://qiita-image-store.s3.amazonaws.com/0/103773/d74c4d12-b21e-6fa0-a404-d70974d7522a.png)

Ruby版を見てて思いついた実装方法がScalaのパーサー・コンビネーター（parser combinators）のライブラリを利用する方法です。
[コップ本](https://www.amazon.co.jp/Scala%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%A9%E3%83%96%E3%83%AB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E7%AC%AC3%E7%89%88-Martin-Odersky/dp/4844381490/ref=pd_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=8GEDVDKXWX48KCJBT7CA)にも紹介されています。
単語が記載される法則を定義して、簡単に変数に変換してくれます。
今回のようなCSVの中にも一定の規則があれば、解析はすごく簡単です。


参考URL)

[http://qiita.com/suin/items/35bc4afe618cb77f80f6](http://qiita.com/suin/items/35bc4afe618cb77f80f6)
[http://seratch.hatenablog.jp/entry/20111010/1318254084](http://seratch.hatenablog.jp/entry/20111010/1318254084)


また、最終的に結果をJSONで返すようなアプリにしたかったので、[Skinny Framework](http://skinny-framework.org/)を利用しています。
が、結局は[Vue.js](https://jp.vuejs.org/)を利用してのサイトになりました。（JSONも返しています）

それではコードの紹介をしたいと思います。

### CSVの解析部分(1)
最初に、引数にCSVの文字列を受け取って、結果を返す部分を紹介します。以下のparseAll(syuku_csv_rule, target) で、CSVの記載規則（syuku_csv_rule）を第1引数で渡して、第2引数で変換対象の文字列も渡しています。

```Scala
def parse(target: String): Either[String, List[SyukujitsuBody]] = parseAll(syuku_csv_rule, target) match {
  case Success(result, _) => Right(result.sortWith((a, b) => a.date.isBefore(b.date)))
  case Failure(msg, _) => Left(msg)
  case Error(msg, _) => Left(msg)
}
```

結果は、Either で返しています。Either は正常（Right）と異常（Left）と言った意味合いで結果を返す事ができます。Rightが英語の"正しい"と同じ意味なので、こういう名称なようです。
Rightには、変換結果（List[SyukujitsuBody]）を日付でソートした結果を設定して返しています。（解析が失敗した）Leftの場合には、エラーメッセージのみです。


いきなり解析の関数のみを説明したのですが、これが"変換処理"の部分です。他にはCSVの記載ルールと変換結果を格納しているクラスのみを定義しているだけです。

変換結果のList[SyukujitsuBody]ですが、SyukujitsuBodyはcase classで定義しています。（ListはScalaのクラスです。）
祝日の名前（date_name）と日付（date）を保持します。

```Scala
case class SyukujitsuBody(date_name: String, date: LocalDate)
```

### CSVの解析部分(2)
あとは記載ルールです。記載ルールは[BNF記法](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AB%E3%82%B9%E3%83%BB%E3%83%8A%E3%82%A6%E3%82%A2%E8%A8%98%E6%B3%95)に似た手法で定義できます。
まずは、ヘッダー部分の2行を定義している箇所を紹介します。

```Scala
private def head1 = """(平成|昭和|明治|大正).*,""".r
private def head2 = """名称,月日(,)?""".r
private def heads = rep(head1) ~ rep(head2)
```
本来はScalaの変数（val or var）で定義すべきかもしれませんが、関数（def）で宣言する必要があります。ひとつ目の"head1"は1行目のヘッダを表現しています。ダブルクォーテーション３つはraw stringと呼ばれるもので、\とか"とかのエスケープが必要な文字を直接記載できます。また、末尾の".r"は正規表現のインスタンスを返す表現です（そのため、"平成|昭和"とかの正規表現で記載）

そして"heads"が２行のヘッダを表現している部分です。rep(x)は繰り返しを表現しており、"x ~ y "（チルダ）は、xの後に yが来ることを表現しています。
これだけで、ヘッダの2行が表現できています。


次に祝日の行の解析部分です。 "syuku_name"と"syuku_date"とはheadと同様に正規表現を適用しています。この２つを結合しているところも"heads"と同様です(syuku_name ~ syuku_date)。
その後の ^^が、headと違うところです。

```Scala
private def syuku_name = """.*?,""".r
private def syuku_date = """[\d]{4}/[\d]{1,2}/[\d]{1,2}""".r
private def syuku_name_and_date_split = syuku_name ~ syuku_date ^^
  {
    case name ~ date =>
      SyukujitsuBody(
        name.dropRight(1),
        LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/M/d"))
      )
  }
```
｛｝で囲っている部分で、祝日名称（name）と日付(date)をcase classの"SyukujitsuBody"に変換しています。このように抽出した文字列を変数に直接代入できます。これを後の記載で、繰り返し抽出しListに格納した結果を返すようにしています。

```Scala
private def syuku_csv_rule = heads ~> rep(syuku_name_and_date_split <~ opt(comma)) <~ rep(tail1 ~ opt(comma))
```

"~>" は"~"と似ていますが、右側（矢印の先）のみを抽出して、左側は解析するけど結果を捨てるようなイメージのルールです。
"<~" は逆バージョンで左側だけを残しています。opt(x)は存在したり、しなかったりです。前述済みですが、repは繰り返しを表現です。

以上で、List[SyukujitsuBody]として結果を返すことができます。

少しズルした気分になりますが、このような簡潔な表現で文字列を解析して、プログラミングで扱いやすい変数に格納することができるのが、Scalaの魅力です。

### 再帰処理（しかも末尾再帰）
一旦、List[SyukujitsuBody]に格納したのですが、私も"年"のハッシュに変換してみたいと思いました。（直接格納する方法が思い浮かばなかっただけです）

最初に思いつたのが、for文版で、色々考えてfoldLeft版、再帰（末尾再帰）版を実装してみました。

まずは、for文版です。一時変数を２つ宣言して、ループを回して存在した場合と、しない場合を分岐しての至って普通の実装方法です。

```Scala
def convertYearMonthMap_for(lst: List[SyukujitsuBody]): SortedMap[Int, SortedMap[LocalDate, String]] = {
  var ret = SortedMap.empty[Int, SortedMap[LocalDate, String]]
  var tmp = SortedMap.empty[LocalDate, String]

  for (itm <- lst) {
    if (ret.isDefinedAt(itm.date.getYear)) {
      tmp = ret.get(itm.date.getYear).get
      if (tmp.isDefinedAt(itm.date)) {
        tmp.updated(itm.date, itm.date_name)
      } else {
        tmp = tmp + (itm.date -> itm.date_name)
        ret = ret.updated(itm.date.getYear, tmp)
      }
    } else {
      tmp = SortedMap.empty[LocalDate, String]
      tmp = tmp + (itm.date -> itm.date_name)
      ret = ret + (itm.date.getYear -> tmp)
    }
  }
  ret
}
```

forもJavaであれば普通なのかもしれませんが、Scalaらしくは無いと感じて色々考えました。

次に考えたfoldLeftですが、今まで一度も使ったことが無かったのですが、初めて実用的な使い方ができました。
初期値（空のSortedMap）を与えて、Listの要素を順番に適応していきます。都度、SortedMapに新規追加をしていき、そのSortedMapを返します。

```Scala
def convertYearMonthMap_foldLeft(lst: List[SyukujitsuBody]): SortedMap[Int, SortedMap[LocalDate, String]] = {
  lst.foldLeft(SortedMap.empty[Int, SortedMap[LocalDate, String]]) { (r, itm) =>
    r.get(itm.date.getYear) match {
      case Some(smap_item) => {
        r.updated(
          itm.date.getYear,
          smap_item.get(itm.date) match {
            case Some(v) => smap_item.updated(itm.date, itm.date_name)
            case None => smap_item + (itm.date -> itm.date_name)
          }
        )
      }
      case None => {
        r.updated(itm.date.getYear, SortedMap(itm.date -> itm.date_name))
      }
    }
  }
}
```

foldLeft版でも、あまり短くならなかったので少しショックでした。更に色々思い出し、再帰で実装しました。
Listをhead(先頭要素)とtail(後続要素)に分割して、headをSortedMapに追加、tailとSortedMapを同じ処理に渡して、全ての要素に同様の変換を実施しています。

```Scala
def convertYearMonthMap_reculsive(
    lst: List[SyukujitsuBody],
    mp: SortedMap[Int, SortedMap[LocalDate, String]] = SortedMap.empty[Int, SortedMap[LocalDate, String]]
  ): SortedMap[Int, SortedMap[LocalDate, String]] = lst match {
    case Nil => mp
    case head :: tail => {
      if (mp isDefinedAt (head.date.getYear)) {
        convertYearMonthMap_reculsive(tail, mp.updated(head.date.getYear, mp.apply(head.date.getYear) + (head.date -> head.date_name)))
      } else
        convertYearMonthMap_reculsive(tail, mp + (head.date.getYear -> SortedMap(head.date -> head.date_name)))
    }
  }
```
割と再帰処理で短く書けて満足です。ようやくScalaしの超初心者から、初心者と中級者の間に入れた気がしました。
更に、末尾再帰を実装するとIntellij IDEAの末尾再帰iconが左側に出るのを初めて知って小さい感動がありました。
もっと短いコードが書けるように頑張りたいと思います。

### そしてVue.js
今回、SkinnyFrameworkにより実装したので、JavaScriptのライブラリを利用した何かを考えていました。当初、Angularを勉強していたのですが、手軽に組み込みにくい印象があり、採用はやめました。そんな中、Vue.jsのサイトを見てみると日本語の説明もあったので、簡単に試してみると数時間で実装することができました。

以下はhtml部分です。ほぼタグだけ。

```html
<div id="app-simple">
    <h3>祝日リスト</h3>
    <a v-for="y in years" href="#" v-on:click="onFunc(y.year, $event)">
        <!-- <div v-bind:class="['btn_default', y.isActive ? 'btn_active': '']"> {{ y.year }} </div> -->
        <div v-if="y.year == select_year"><div class="btn_default btn_active"> {{ y.year }} </div></div>
        <div v-else><div class="btn_default"> {{ y.year }} </div></div>
    </a>
    <div class="clearfix"></div><br />
    <div v-for="m in err_msg"  class="alert alert-danger" role="alert">{{ m }}</div>
    <hr/>
    <ol>
        <li v-for="s in syukujitsu" v-if="s.date.slice(0,4) == select_year">
            <div > {{ s.name }}:{{ s.date }} </div>
        </li>
    </ol>
</div>
```

{{xxx}}はVue.js の変数を表示する置換定義です。あとv-for, v-ifのタグの属性で繰り返しや条件分岐が表現できています。すごく簡単です。

以下は、各要素へのデータバインディングの適用です。

```javascript
/*
 * Vue.js
 */
var app = new Vue({
        el: '#app-simple',
        data: {
            years: years_list,
            syukujitsu: syukujitsu_default,
            select_year: "2016",
            err_msg: server_msg
            },
        methods: {
                onFunc: function(y, e) {
                    //console.log(app.default_year)
                    app.select_year = y; // data binding

                    // no good data bind
                    for(var i = 0; i < years_list.length; i++){
                        if(years_list[i].year == y){
                            years_list[i].isActice = true;
                            // console.log(years_list[i].year + ":" + years_list[i].isActice);
                        }
                        else
                        {
                            years_list[i].isActice = false;
                            // console.log(years_list[i].year + ":" + years_list[i].isActice);
                        }
                    };
                    app.years = years_list; // no good data bind
                }
            }
        });
```
app.select_year = y; を実行するだけで、祝日の一覧再表示が勝手に実施されました。めちゃくちゃ便利です。

私のように
+View層だけで簡単に試したい
+Html,CSS,JavaScriptの知識が多少ある
+npmとか詳しくない
場合はすぐに始められると感じました。

また、公式ページの日本語情報も充実しているように感じました。ここ数年のJavaScriptのデータバインディングの話題をニュースでは見ていたのですが、いまいち実感がわいていませんでした。Angular2のTUTORIAL: TOUR OF HEROESもやってみていたのですが、イマイチ感動が薄かったです。今回、こんなに簡単に表示が切り替わるのが体感できて嬉しかったです。

**注記**
以下だけで、選択状態を表現したかったのですが、私の知識が足りずに実現できずにいますのでご注意ください。

```html
<div v-bind:class="['btn_default', y.isActive ? 'btn_active': '']"> {{ y.year }}
```


### まとめ
Rubyでの解析と単純に比較はできませんが、感じた事を書いてみます。
・CSVを読み込むライブラリがあるのがすごい。
・foreach, map はScalaでも出てくる（動作も少し似ている？）
・"|xxx, yyy|"で展開ができている？（すごい簡単！）
・日付変換できない時の"Date.parse(text) rescue nil"って表現も簡潔
・transpose が若干難しい
・Hashキーの有無確認無しでも吸収してくれている？（hash[parsed_date] = name）

書いていると興味がつきないけど、今はもっとScalaを身につけます。

ちなにソースはgithubで公開しています。
[https://github.com/matsutomu/parse-syukujitsu-scala](https://github.com/matsutomu/parse-syukujitsu-scala)

２年間勉強していますが、まだまだ初心者です。ご指摘お待ちしています。

### おまけ
当初、『JSONで・・・』も考えていたのですが、とりあえず以下のURL叩いて結果が返ってくるまで実装できています。
http://localhost:8080/syukujitsu.json/2017