ミーハーな読者なら、barbiesというライブラリをご存知の方も多いと思う。そう、HKDを扱う定番ライブラリだ。HKDは、同アドベントカレンダーにも寄稿されている他、Haskell Dayでも紹介された(https://assets.adobe.com/public/b93f214d-58c2-482f-5528-a939d3e83660)注目の技法だ。Higher-Kinded Data (HKD) について - Qiita

HKDは、一番簡単な場合であるはずのIdentityを使うと着脱が面倒になるという問題がよく知られている。 Data.Barbie.Bare モジュールの Wear という型族を使って定義すれば、それを簡単にはがせ、普通のレコードと全く同じように使える。

data Barbie t f = Barbie { name :: Wear t f String , age :: Wear t f Int } instance BareB Barbie instance FunctorB (Barbie Covered) instance TraversableB (Barbie Covered) instance ProductB (Barbie Covered) instance ConstraintsB (Barbie Covered) instance ProductBC (Barbie Covered) bstrip :: b Covered Identity -> b Bare Identity bcover :: b Bare Identity -> b Covered Identity

とても便利だが、いくつかの不満点が残っている。まずフィールド1つにつき Wear t f を被せないといけないのは面倒で、可読性の面でもよくないし、ジェネリクスとはいえインスタンス宣言をずらずら書くのも面倒極まりない。また、Bare関係なしに、フィールドの数が多いとコンパイル時間が増えるばかりか、インライン化に失敗して実行時の効率まで悪化する可能性も高い。20個程度のフィールドを扱うコードで2分以上かかる例もあり、インスタンス定義だけでなくメソッドを使っている場所にも影響する。

こんなことで消耗するくらいなら、全部Template Haskellという爆弾で解決してしまえば良い、というコンセプトで作ったのがbarbies-thだ。使い方は簡単、Template Haskellを有効にし、 declareBareB を普通のデータ型の定義に被せればよい。

import Data.Barbie.TH declareBareB [d | data Barbie = Barbie { name :: String , age :: Int } | ]

すると、あら不思議、何もかもいい感じに揃えてくれる。

data Barbie sw h = Barbie { name :: Wear sw h String, , age :: Wear sw h Int } deriving Generic instance BareB Foo instance FieldNamesB (Barbie Covered) where bfieldNames = Foo (Const "name" ) (Const "age" ) instance ProductB (Barbie Covered) where ... instance FunctorB (Foo Covered) where ... instance TraversableB (Foo Covered) where ... instance ConstraintsB (Foo Covered) instance ProductBC (Foo Covered)

主要なインスタンスについても、ジェネリクスではなくTemplate Haskellによって実装を導出しているため、コンパイル時間や最適化に対する心配もいらない。HKD(barbies)を実用する上での障害を一通り解決するというわけだ。おまけとして、全フィールド名を t (Const String) の形で取得できる FieldNamesB というクラスも用意しており、 FromJSON などのインスタンスを定義するのに役立つ。

これからは、ジェネリックプログラミングやテンプレートメタプログラミングを活用し、仕事の半分はデータ型を書くだけで終わらせてしまおう。それでは、Happy higher-kinded holiday!