barbies-thで気軽にHKDを堪能しよう [Haskell AdC 14]

ミーハーな読者なら、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!

hackage.haskell.org