Functional Java の HList を C# に移植して F# 拡張を作った

前々からFunctional JavaにHListが存在するのは知っていたのですが、なかなか手をだせていませんでした。 で、最近Lensライブラリ移植の息抜きにちょっと時間があったので移植してみました。

そもそもHListって?

heterogenous list。 日本語ではたまにヘテロリストとも呼ばれてるみたいですね。 "haskell HList"とか"HList shapeless"で検索すれば何かしら情報は手に入ると思います。

というわけで作った

下記リポジトリから取得できいます。 NuGetにも公開しているので簡単に試すこともできます。

pocketberserker/Data.HList · GitHub

移植自体はそんなに難しくなかった…嘘ですごめんなさい。

実は最初は Pure F# で実装していたのですが、型推論がだめな方向に作動してしまって型エラーをなんとかするのが面倒になった経緯があります。 以下残骸。

Pure F# で HList を実装しようとしたが…型推論に阻まれる

Apply.Cons に型を書けば問題なく通るかもですが、それやるのであればいっそもうC#で実装したほうがいいんじゃない、ということでC#にスイッチしました。

使い勝手は?

HaskellScala(shapeless)のそれに比べると使い勝手は悪いかもしれません…というかまず機能の差が。 それに彼らはこちらにはないHTやマクロ、型クラスがありますからね。


気をとりなおして、JavaC#とF#の使い勝手について。

これは完全に型推論の力に依存して使いやすさが変わります。 サンプルコードを見ればその違いがよくわかるので、いかにリンクを並べておきます。

HAppendの例だけなのと、C#の実装がこれでよいのか自信がないのであれですが、C#...。

Javaは変数の型を書くのはだるいものの、型は比較的わかりやすいためそんなに考えなくてもなんとかなります。 が、C#メソッドのほうが推論されないため、4つの型パラメータに何を当てはめれば望みの型になるのか考える必要があります。 F#もC#と同じ道をたどるかと思いきや…この程度のコードだと空気を読んで型を推論してしまいます*1

まとめ

  • C#とF#でもHList使えるよ!
  • 本記事とは若干関係ないけど、HListはジェネリクスの勉強に役立つ(要出典、要検証)
  • FoldrのF#拡張とサンプルを作りたい人生
  • map関数を作りたい

*1:推論に頼りすぎると前述みたいな型エラー事案に遭遇するのでご利用は計画的に

AppVeyorのSQL ServerにJDBC Driverで接続する

備忘録です。 簡単な経緯は以下の通り。

rpscala でそういう話題があったのに関連して、 AppVeyor で頑張れないかを xuwei さんのコードを引き継いで試してみました。

参考資料

だいたいこの辺りを情報を AppVeyor で実行できるようにしていきます。

設定やスクリプト

説明が面倒なので結論だけはります。 設定値やプロジェクト名もそのままになっているので、適宜読み替えてください。 また、今回の内容と直接関係のない部分は削っています。

あと、sbtはリポジトリに同梱しているパターンで試しているので、同梱したくない場合はsbtをダウンロードする必要もあります。

# appveyor.yml
services: mssql2014
build_script:
- netsh advfirewall firewall add rule name="jdbc driver for sql server udp" dir=in action=allow protocol=UDP localport=1434
- sc config sqlbrowser start= auto
- net start sqlbrowser
- ps: Set-ExecutionPolicy RemoteSigned
- ps: .\Setting-SqlServer.ps1
- net stop "SQL Server (SQL2014)"
- net start "SQL Server (SQL2014)"
- netsh advfirewall firewall add rule name="jdbc driver for sql server tcp" dir=in action=allow protocol=TCP localport=1433
- sqlcmd -S ".\SQL2014" -U sa -P Password12! -Q "create database scalikejdbc;"
- del scalikejdbc-core\src\test\resources\jdbc.properties
- move scalikejdbc-core\src\test\resources\jdbc_sqlserver.properties scalikejdbc-core\src\test\resources\jdbc.properties
- ps: wget http://download.microsoft.com/download/0/2/A/02AAE597-3865-456C-AE7F-613F99F850A8/sqljdbc_4.1.5605.100_enu.tar.gz -OutFile "sqljdbc.tar.gz"
- 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar.gz
- 7z x -y -oC:\projects\scalikejdbc\ sqljdbc.tar
- ps: mkdir .\scalikejdbc-core\lib
- ps: cp .\sqljdbc_4.1\enu\sqljdbc41.jar .\scalikejdbc-core\lib\
// Setting-SqlServer.ps1

// バージョンに合わせて ComputerManagementの値を差し替える
// SQL Server 2008 -> ComputerManagement10
// SQL Server 2012 -> ComputerManagement11
Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocol |
  Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp'} |
  ForEach-Object {$_.SetEnable()}

$tcpProperties =
  Get-WmiObject -Namespace root\Microsoft\SqlServer\ComputerManagement12 -Class ServerNetworkProtocolProperty |
    Where-Object {$_.InstanceName -eq 'SQL2014' -and $_.ProtocolName -eq 'Tcp' -and $_.IPAddressName -eq 'IPAll'}

foreach( $tcpProperty in $tcpProperties ){
  $requestedValue = ""
  if($tcpProperty.PropertyName -eq "TcpPort"){
    $requestedValue = "1433"
  }
  $tcpProperty.SetStringValue($requestedValue)
}

SQL Server 2014 Espressで試していますが、たぶん SQL Server 2008あたりまでは普通に動きそうな気がします。

重要なところ?

SQLServer用のjarを同梱するのはライセンス的につらい感じがしたので、PowerShell 3.0から使える wget エイリアスコマンドを使ってダウンロードし、AppVeyor にインストールされている 7Zip で解凍するのがミソです。

あと、各種設定をして再起動する前にデータベースを作成してもログイン権限がなくてテーブルにアクセスできないかも?

まとめ

今回はsbtで作業しましたが、gradleでもできるのではないでしょうか。

懸念事項は、AppVeyorの実行速度が遅いことと、mysqlやpotgresql前提で書かれたSQLSQL Serverで通らないことがまれによくあることです。

CoArbitraryの実装比較

全国 QuickCheck 系ユーザの皆様こんにちは、もみあげです。

今日は

CoArbitraryとScalacheck - scalaとか・・・

に関連して、各言語の QuickCheck 実装の CoArbitraryArbitrary (a -> b) インスタンスについてみていきましょう。

CoArbitrary とは

QucikCheck のドキュメントによると、関数値の生成に使われる型クラスらしいです。

-- Haskell での定義
class CoArbitrary a where
  coarbitrary :: a -> Gen b -> Gen b

値と Gen から新たな Gen を生成できます。 第2引数の Gen が必要なのは、最終的な値を生成するための Gen を作るために、乱数生成器が必要だからだと思ってます。

CoArbitraryインスタンスを作るときには、 variant という関数を用いることが多いです。

-- Haskell での定義
Integral n => n -> Gen a -> Gen a

この関数は Gen の seed を変更するものです。

詳しく知りたい場合はソースコードを読みましょう。

Test/QuickCheck/Arbitrary.hs

Arbitrary (a -> b)

では、僕らの欲したインスタンスを見てみましょう。

-- Haskell での定義
instance (CoArbitrary a, Arbitrary b) => Arbitrary (a -> b) where
  arbitrary = promote (`coarbitrary` arbitrary)

promote 関数という関数は

http://hackage.haskell.org/package/QuickCheck-2.7.6/docs/Test-QuickCheck-Gen-Unsafe.html

Monad に包まれた GenMonad な値を生成する Gen にあげる関数です。 lift と思ってもよさそうに見えますね。

promote に適用する関数値の型は a -> Gen b であり、 関数も Monad なので条件を満たしています。

流れをまとめると

  • 適用された値をから戻り値を生成するジェネレータの seed を変更する関数を作る
  • promote で lifting させる
  • あたかも関数を生成したようにみせかけて property に渡す
  • 関数適用したら Gen から戻り値がランダムに生成される

といったところでしょうか。

なお、一連の情報からこのインスタンスから得られる関数値の適用結果を使って何かテストするのは避けるべきということが伝わってくる気がします。

実装比較

てきとーに解説を済ませたところで、実装をみていきましょう。

Functional Java

https://github.com/functionaljava/functionaljava/blob/v4.2/core/src/main/java/fj/test/Coarbitrary.java

https://github.com/functionaljava/functionaljava/blob/v4.2/core/src/main/java/fj/test/Gen.java#L637

promoteMonad ではなく、1引数関数クラス F が生成される Gen を返します。

purescript-quickcheck

https://github.com/purescript/purescript-quickcheck/blob/v0.4.0/src/Test/QuickCheck.purs#L25-L26

https://github.com/purescript/purescript-quickcheck/blob/d464c5c31313ea6737ac46a8335596eeefe74550/src/Test/QuickCheck/Gen.purs#L49

promoterepeatable という名称なのと、 Monad ではなく関数が対象です。

idris-quickcheck

https://github.com/david-christiansen/idris-quickcheck/blob/51b7f619a3203c20ef8d8e30007bd83dba9fd386/QuickCheck.idr#L96

Arbitrary がほしいなら双対も一緒に定義しろ、という潔さです。

SwiftCheck

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Arbitrary.swift#L365-L367

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Gen.swift#L115

https://github.com/typelift/SwiftCheck/blob/00d6a7f5482da1671e2dd56f71068d6e33a8bdc8/SwiftCheck/Combinators.swift#L13

promoteMonad でも関数でもなく Rose が対象です。

Persimmon.Dried

今日*1実装しました。

https://github.com/persimmon-projects/Persimmon.Dried/pull/2

たぶん動いています。

Gen.Apply'T option を返すので、こいつをなんとか 'T にする苦肉の策として こんな感じにごまかしてます。

FsCheck

https://github.com/fsharp/FsCheck/blob/3d7f4d47d6cff65ecdbcc31e60f007b54012de9f/src/FsCheck/Arbitrary.fs#L482

考え方はかわらないけど、実装が根本から異なるので気にしない方向で。

まとめ

  • CoArbitrary の考え方は共通している
  • Idris のみ Arbitrarycoarbitrary の実装を要求する
  • variant 関数はシグネチャは同じだが実装はばらばら(乱数生成器に依存する?)
  • promoteMonad か関数か Rose が対象
  • 実装はそんなに難しくない?

だいたい似たような実装になっていますが、Gen の構造が他と異なる Persimmon.Dried が若干強引な印象です。 実際、Persimmon.Dried の移植元である scalacheck でも以下の issue で同様の指摘がなされています。

https://github.com/rickynils/scalacheck/issues/136

scalacheck の動向に注視したいところですね。

それはそれとして、Functional Java やばい(褒め言葉)。

*1:ブログ投稿日 or pull request マージ日参照

各言語でtype classesを実装する際に使いそうな道具とか

現時点における GHCScala、F#、Java の type classes 実装に使う道具比較をメモしておきます。

言語比較したいわけではなく、論争を繰り広げたいわけでもないのであしからず。

あとてきとーに書いてる感は否めないので、間違っている言葉、表現、概念などあれば適宜突込みをお願いします。

高階型

  • GHC: m a
  • Scala: M[A]
  • F#: インターフェース _1<'M, 'A> を用意して擬似的に表現
  • Java: インターフェース _1<M, A> を用意して擬似的に表現

F# や Java に関しては以下の記事を参照してください。

JavaでFree Monad - scalaとか・・・ F# で高階型のエミュレーション - pocketberserkerの爆走

ランクN多相的な何か

末尾再帰最適化

  • GHC: よく知らない(そもそも lazy evaluation だし…)
  • Scala: あり。 tailrec annotation をつけておくと末部再帰になっていない場合コンパイルエラー。
  • F#: あり。次のバージョンあたりで Scala の tailrec annotation みたいな機能追加したいねとか言っていたような…
  • Java: なし

型クラスのインスタンス作成

表現が思いつかなかった…とりあえず Maybe での例

-- GHC
instance Monad Maybe where
...
// scalaz の Maybe での例
new Monad[Maybe] {
// F#
type Option = Z
{ new Monad<Option.Z>

F# の場合、型パラメータの制約に hoge メソッドを持つ、とかできるので、高階型をエミュレーションしない場合はこの書き方にならない。 が、あれを説明するのは面倒なので FsControl というライブラリを検索してください。

Java は inner class に型クラスを継承させるのがやりやすい、だったかな?

型引数が2つある場合

Scalaは今後 type lambdas というシンタックスシュガーが入るかもしれない?

このあたりは F# が不利。 型をいい感じにできるのはもしかしたら type erasure 方式の利点かもしれない。

https://github.com/pocketberserker/FSharp.Karma/blob/9d6aab537793875727f8625acf146e9cbf9931cf/FSharp.Karma/CoYoneda.fs#L13

これと

https://github.com/xuwei-k/free-monad-java/blob/aa7ec1c2897d0982c406524082cd9f8c6fefed37/free/src/main/java/free/Coyoneda.java#L3

これとかの比較だと、F# は型が残る上に変位指定ができない(できるならだれか教えてください)から、下手に obj も使えなくて悲しいことになっていたりします。 Java版はそのあたりすっぱり割り切っている。

型推論

これに関してはあまり書きたくない…。

どの言語にもあるけど、どこまで推論されるかは言語次第だよ、くらいでいいですかね? (指摘受けそうだなぁ…)

型クラスライブラリとか

  • GHC: Hackage 調べましょう
  • Scala: Scalaz が有名ですね
  • FsControl, FSharp.Karma
  • Java: Functional Java, highj, free-monad-java

おわりに

variance とか GADTs とか代数的データ型とかパターンマッチとか色々書いていない気がするけれど、気が向いたらで…。

あ、OCaml がないのは書いたことないからです…突っつかれたら勉強します。

F# で高階型のエミュレーション

この記事では、F# で 高階型 (higher kinded types) をエミュレーションすることについて記述します。

本当は 函数型なんたらの集い 2014 in Tokyo - connpass で話そうと思っていたのですが、運営を考えると発表できる気がしなくなった*1ので、記事にしてお茶を濁します。

注意事項

  • F# で推奨される実装、ガイドラインから逸脱した実装になっています
  • 型安全かどうかは考察しません(時間が足りない)
  • この記事をみただけで"F# は残念な言語"という判断を下すのは誤りです
  • 専門家ではないので、厳密なことは書けません。生暖かい目で見守ってください。
  • Haskell, Scala(z), Java のコードも登場します

参考文献?

本記事に登場する FSharp 実装まとめ

本記事で実装した型に幾つか修正、追加を行ったライブラリが下記になります。

pocketberserker/FSharp.Karma · GitHub

問題点: この世界に奴はいない

世の中には高階型というものが存在します。高階型については、

(もりそば)Scalaによる高階型変数 - Higher-kind Generics - ( ꒪⌓꒪) ゆるよろ日記

とかを読んでみてください。

高階型が存在しないとどういう問題点があるのかという話については、

関数を扱えるだけでは、モナドを表現するには不十分過ぎる - scalaとか・・・

を読みましょう。

さて、 F# も高階型をサポートしていない言語の一つです。

抽象化や共通化のみを考える場合、F# には overload や"静的に解決された型パラメータ"を使えば、解決できる部分もあります。 これらの機能を使って実装されたライブラリとしては

gmpl/FsControl · GitHub

があります。

しかし、型コンストラクタで高階型が要求されるような型クラスは、高階型がなければ実装が困難です。*2

解決案: ないなら…作るしかねぇ!

参考文献にあげた highj や higher、 free-monad-java では 高階型をエミュレーションするための型を用意しています。 この方法は、F# でも利用可能です。 本記事では free-monad-java のスタイルを採用して記述していきます。

// ScalaF[A] を表す型
type _1<'F, 'A> = interface end

_1 は 型パラメータを一つ持つ高階型です。 'F は対象の型を、 'A はその型がもつ型パラメータを表します。

今回は highj における μ の代替として判別共用体を用います。

例: Identity

最も簡単な(そして標準に存在しない型の)例として、Identity型を定義してみましょう。

// 同名の判別共用体を用意
// highj の Id.μ 、 free-monad-java の Id.z 相当
type Id = Id

// 実際の Idtype Id<'A> = { Value: 'A }
  with
    interface _1<Id, 'A>

まず、Id 型を表す高階型用の判別共用体 Id を用意します。

次に、実際に利用する Id 型に _1 を実装します。 こうすることで、その型が高階型であることを表現出来ます。

あとは、例えば Functor などを用意してあげれば、

type Functor<'F> =
  abstract member Map: ('A -> 'B) * _1<'F, 'A> -> _1<'F, 'B>

module Functor =

  // 任意の Functor を対象にできる
  let map (fa: Functor<_>) f a = fa.Map(f, a)

ダウンキャストによって、それなりに抽象化できます。

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Id =
  // functor は F# の予約語なので使用できない
  let functor_ = { new Functor<Id> with
    // fa は _1<Id, 'A> なので、Id<'A> ダウンキャスト可能
    member this.Map(f, fa) = (fa :?> Id<_>) |> map f :> _1<Id, _> }

例: Free

ここまでは、F# だと overload と 静的に解決された型パラメータを用いればよい話でした。

しかし、 Free Monad の実装には高階型が必要になるので、型パラメータでなんとかなるとは限りません。 Free Monad については検索したら色々と記事があるので探してください。

Haskell 実装

まずは、Free MonadHaskell 実装から見てみましょう。 今回は ekmett 氏の free を参考にします。

https://github.com/ekmett/free/blob/a2008da7fe75b4cc80904d9bd3275e5772a28c91/src/Control/Monad/Free.hs#L103

f が高階型になっており、 Free 型コンストラクタ内で Free 型を持っています。

Scala 実装

Scala では Scalaz のものが有名です。

https://github.com/scalaz/scalaz/blob/2f80af1f512d80a2a531c1859f58b09f4002da1d/core/src/main/scala/scalaz/Free.scala#L50

S が高階型となっています。 Haskell での Free 型コンストラクタに対応する Suspend 型コンストラクタをみると

https://github.com/scalaz/scalaz/blob/2f80af1f512d80a2a531c1859f58b09f4002da1d/core/src/main/scala/scalaz/Free.scala#L17

Free を持つことがわかります。

_1 を用いてエミュレーション

Suspend を _1 を使って表現すると、次のようになります。

type private Suspend<'F, 'A> (a: _1<'F, Free<'F, 'A>>) = ...

あとはうまい具合に型を合わせてあげれば、Free Monadの完成です。 これに関しては本記事の内容ではないので、もっと詳しく知りたい方は

JavaでFreeモナドを表現するためのテクニックやexistential type(存在型)の話 - scalaとか・・・

を読んでください。 Java と F# での違いは

  • F# は末尾再帰最適化される
  • F# は type erasure ではないため、型パラメータの型が異なっていると InvalidCastException
  • F# にてきとーに型推論させると、Gosubの型パラメータが obj に推論されて死ぬ(ので、結局明示しないとだめ)

あたりです。

問題点

  • mixin がないので、既存の型に 1 を実装できない(Wrapper 型に対して 1 を実装するはめに)
  • 明示的にキャストする必要があるので、ダウンキャストやアップキャストの嵐に

次回予告

もみあげ「やった、Free Monadができた...ん?」

GitHub「xuwei-k pushed to develop at xuwei-k/free-monad-java operational monad

もみあげ「ナンデ!?ワイルドカードナンデ!?」

*1:想定以上に大規模になった...

*2:試していないので絶対にできないとは言えない。もしかしたら Map を持つ型などの制約をつければ実装できるかも…?

「Java開発者のための関数プログラミング」読んでみた

O'Reilly Japan - Java開発者のための関数プログラミング

最近こういう系統の本が増え始めた気がしますが、オライリー電子書籍から発売されるとは思っていなかった。
そんなわけで、気になったので買って読んでみた。

ちなみに原著者のDean Wampler氏はバク本の著者の一人でもある(読み終わってから気がついた)。

プログラミングScala

プログラミングScala

感想

訳者の方が書かれていることと大体同じことをおもった。

「Java開発者ための関数プログラミング」が出版されました - YAMAGUCHI::weblog

特に「6章 ここからどこへ向かうべきか」には参考文献の紹介が多く、これをきっかけにもっと学んでいってほしいというのが原著者の考えかな、とも思ったり。

本書には載ってない良書な日本語書籍としては

プログラミングの基礎 (Computer Science Library)

プログラミングの基礎 (Computer Science Library)

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

この2冊が入りやすいかなと。

個人的な

Javaオンリーで触っている人でライトに学びたいならおすすめかも。
あとは他人におすすめするために読んでみるというのもアリかなとか。Java知っていれば読めるというのはハードル下がる可能性はありますし、おすすめしやすくなると思う。

いやしかし、久々にJavaで頑張ってるコードを見た。あと「nullがない安全な言語設計」を導入できない不幸な部分も見た気もする。

以上、あっさりした感想でした。

JavaCC,ANTLR,パーサコンビネータを触ってみた

某場所にて以下のような課題が出た。

  • いわゆるΣ計算の構文を解析するParserと解析結果にあわせた処理を行うVisitorの作成
  • 書式は sum i : NUMBER .. NUMBER of expression
  • 変数はiで固定、iを1つ目のNUMBERから2つ目のNUMBERまで変化させながらexpressionを計算
  • expressionは四則演算を行う。括弧を使用できること。
  • jjtreeを用いて実装せよ

たとえば"sum i : 3 .. 5 of 1 + 2 * i"が27になる、みたいな形ですね。
でまぁ、jjtreeというかJavaCCだけだと

戦略というか方向性というか

  • 時間短縮のため変数iはキーワードとしておく
  • 変数が1つしかない仕様なので記号表は作らない
  • Visitorのフィールドでiの当該値を記憶する

あとはその場の勢いで追加実装したりしています。

JavaCC,jjtreeで

コードは諸事情のため略。自分で書いたコードは170行くらい。
12月31日と1月1日で3時間ずつほど消費。割と問題が起こりそうにないところではまったり、typoしてジェネレートやり直しなどで時間をとられた気がする。

ANTLR

年末から↓の本を読みはじめていたので、軽い気持ちで作ることに。

言語実装パターン ―コンパイラ技術によるテキスト処理から言語実装まで

言語実装パターン ―コンパイラ技術によるテキスト処理から言語実装まで

コードは次のようになりました。

ANTLRのtree grammarでΣ演算

動かすのに必要なものは

Visitorではなく、tree grammarを使って木構文解析器をジェネレートする形にしました。行数は102行。
tree grammarの書き方がわからずに四苦八苦していたら1月2日が終わっていました…

JParsecで

そういえば、この課題が出た当初にこんなやり取りがあったことを思い出す。

思い出してしまったらやるしかないですよね。

JParsecでΣ演算

動かすのに必要なものは

  • jparsec-2.0.jar
  • cglib-nodep-2.2.2.jar

JParsecで作ったやつだけ追加実装で乗算記号"*"を省略しても乗算が計算できるようにしてあります。320行。
ANTLRのときとは別の意味で書き方がわからず、チュートリアルやサンプルコード読んでいたら1月3日が通り過ぎていました…

Scalaのパーサコンビネータライブラリで

上記3つでかなり精神的に消耗したため、癒しを求めてScalaでも試すことに。

ScalaのパーサコンビネータでΣ演算

98行。ScalaちゃんマジScala。2時間もかかっていないはず。

おもったこと

言語は異なるものの、id:bleis-tiftさんが下記資料にかかれていること(83ページあたり以降)を実感しました。

  • パーサジェネレータはホスト言語とはジェネレータ用の言語を覚える必要がある
  • パーサジェネレータだと生成してコンパイルしなければならない
  • パーサジェネレータで生成したコードは
  • JParsecのコードを読むのはつらい(というか読める気がしない…)
  • コード量が膨らむ

あと、パーサジェネレータは内部でホスト言語のコードも書かなければならなくなる場合も多いのでなんだかなぁと思ったりしますね。
JParsecを使ったコードは、今回実装したものが四則演算レベルなのでなんとか読めるレベルで収まっていますが、もっと複雑なことをしようとしたらわけがわからなくなると思われます。GroovyからJParsecを使えば読みやすさは多少改善できるかもしれませんが、パターンマッチやcase classがないのでデータ構造に関しては楽できないと思います。
あと、パーサコンビネータは小さいパーサを組み合わせて大きなパーサを作るので、部分部分のテストもしやすいです。

コードの解説は?

心に余裕ができたら書きます…