Higher-Kinded Data (HKD) について

これは Haskell Advent Calendar 12 日目の記事です。今日は 12/12+12 日です。

今回は、最近流行りの Higher-Kinded Data (HKD) について簡単に紹介したいと思います。

// 以前、某社の採用ビアバッシュ的イベントで、Haskell ほとんど知らない人の中で HKD の話をした際のスライドを公開し忘れていたので、せっかくなので多少手直しして公開します。

Haskell Day 2019 でも、@fumieval さんによる HKD のトークがあったので、そちらも参考にすると良いと思います。
https://twitter.com/fumieval/status/1193016480574500865

## 設定はモノイド

Haskell 界隈には設定はモノイドであるべきという一派があります。

次のようなプログラムの設定を表すデータ型を例にして考えます。

```haskell
data Setting = Setting
    { verbose :: Bool
    , host :: String
    , port :: Int
    , timeout :: Int
    } deriving (Generic, Eq, Show)
```

このデータの入力元は、環境変数や設定ファイル、コマンドラインオプションなどが考えられます。この例だと項目が 4 つしか無いので、これらの情報源から Setting を構築するのはさほど大変ではないですが、項目が増えてくると面倒です。

Setting のモノイド版が定義できれば、モノイド版で合成してから Setting を構築することができるので便利そうです。

そこで `Setting` 型の全てのフィールドが [Last](http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Monoid.html#t:Last) で囲まれた `SettingPartial` 型を考えます。

```haskell
data SettingPartial = SettingPartial
    { verbose_ :: Last Bool
    , host_ :: Last String
    , port_ :: Last Int
    , timeout_ :: Last Int
    } deriving (Generic, Eq, Show)
```

`SettingPartial` は全てのフィールドが Last で包まれているので、Semigroup と Monoid のインスタンスを簡単に定義することができます。

```haskell
instance Semigroup SettingPartial where
    (<>) = mappenddefault
instance Monoid SettingPartial where
    mempty = memptydefault
```

ここでは [generic-deriving](http://hackage.haskell.org/package/generic-deriving) を使い、面倒な記述を減らしています。

次に、設定ファイルやコマンドライン引数のパーサーを `Setting` のかわりに `SettingPartial` を返すように実装し、デフォルト値を定義しておきます。

```haskell
getSettingFromEnvironment :: IO SettingPartial
getSettingFromEnvironment = ...
getSettingFromFile :: IO SettingPartial
getSettingFromFile = ...
getSettingFromCommandLine :: IO SettingPartial
getSettingFromCommandLine = ...

defaultSetting :: SettingPartial
defaultSetting =
    SettingPartial
    { verbose_ = Last (Just False)
    , host_ = Last Nothing
    , port_ = Last Nothing
    , timeout_ = Last (Just 60)
    }
```

後は Monoid の力でこれらを簡単に組み合わせることができます。

```haskell
getSetting :: IO SettingPartial
getSetting = do
    settingFromEnv <- getSettingFromEnvironment
    settingFromFile <- getSettingFromFile
    settingFromCmdLine <- getSettingFromCommandLine

    return (defaultSetting <> settingFromEnv <> settingFromFile <> settingFromCmdLine)
```

Last で包んだおかげで、デフォルト値 < 環境変数 < 設定ファイル < コマンドライン引数の優先順位で設定が合成できています。
SettingPartial のままだと全てのフィールドが Last で包まれてしまっているので、プログラム本体から扱いやすいように SettingPartial を Setting に戻す必要があります。

```haskell
validate :: SettingPartial -> Last Setting
validate s =
    Setting <$> verbose_ s
            <*> host_ s
            <*> port_ s
            <*> timeout_ s
```

各種の設定をモノイドにしておくことで、設定項目それぞれについて、複数の設定情報源がある場合についても一貫性のある形で設定値を合成することができました。
一方で、実際のプログラムではより多くの設定項目があるため、いちいち Setting と SettingPartial のように 2 つのデータ型を定義するのは面倒になってくるかもしれません。
また、各パーサーにおいては `Last a` といった型よりも `Maybe a` や `Either err a` などといった型の方が便利な場合があり、`SettingPartial` だけでは不足かもしれません。

そこで登場するのが Higher-Kinded Data です。

## Higher-Kinded Data とは

先ほどの `SettingPartial` の `Last` を、型パラメータ `f` として取る `Setting_` 型を考えてみましょう。

```haskell
data Setting_ f = Setting_
    { verbose_ :: f Bool
    , host_ :: f String
    , port_ :: f Int
    , timeout_ :: f Int
    } deriving Generic

type SettingPartial = Setting_ Last
type Setting = Setting_ Identity
```

f は `f String` のように使われているので、f の kind は `* -> *` で、`Setting_` の kind は `(* -> *) -> *` であることがわかります。

元々の Setting の定義も、[Identity](http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Functor-Identity.html#t:Identity) を使って `Setting_ Identity` としてしまえば、使う側で runIdentity する必要がありますが、重複した定義をする必要がなくなります。もちろん、Maybe 版が欲しければ `Setting_ Maybe` とするだけです。



なるほど便利そうです。しかし、実際には一筋縄では行きません。

例えば、`Setting_` と `SettingPartial` を見比べると、 deriving 節から `Show`, `Eq` が消えていることが分かります。
試しに指定してみると、、

```
example.hs:110:26: error:
    • No instance for (Show (f String))
        arising from the second field of ‘Setting_’ (type ‘f String’)
      Possible fix:
        use a standalone 'deriving instance' declaration,
          so you can specify the instance context yourself
    • When deriving the instance for (Show (Setting_ f))

example.hs:110:32: error:
    • No instance for (Eq (f String))
        arising from the second field of ‘Setting_’ (type ‘f String’)
      Possible fix:
        use a standalone 'deriving instance' declaration,
          so you can specify the instance context yourself
    • When deriving the instance for (Eq (Setting_ f))
```

`f String` や `f Bool` が、`Eq` や `Show` のインスタンスと判定できないよと怒られてしまいました。
GHC のエラーメッセージは親切にも、 standalone な deriving instance 定義を使えばコンテキストを指定できるよと教えてくれているので、その通りにすれば `Eq`, `Show` を導出できます。

```haskell
deriving instance (Show (f Bool), Show (f String), Show (f Int))
    => Show (Setting_ f)
deriving instance (Eq (f Bool), Eq (f String), Eq (f Int))
    => Eq (Setting_ f)
```

いや、これは面倒ですね。。

## barbies

ですが安心してください。もちろん Haskell には [barbies](https://hackage.haskell.org/package/barbies) という便利なライブラリがあり、Generics の力によりボイラープレートを劇的に減らすことができます。

```haskell
data Setting_ f = Setting_
    { verbose_ :: f Bool
    , host_ :: f String
    , port_ :: f Int
    , timeout_ :: f Int
    } deriving (Generic, FunctorB, TraversableB, ProductB, ConstraintsB, ProductBC)

deriving instance AllBF Show f Setting_ => Show (Setting_ f)
deriving instance AllBF Eq   f Setting_ => Eq   (Setting_ f)
```

前の定義と異なり、standalone deriving のコンテキストで各フィールドの型についての言及をするかわりに、`AllBF Show f Setting_` などと書くだけで良くなっていることが分かります。

barbies の力はこれだけではありません。例えば、先ほどの `validate :: SettingPartial -> Last Setting` の HKD 版は、barbies で定義されている [`bsequence'`](https://hackage.haskell.org/package/barbies-1.1.3.0/docs/Data-Barbie.html#v:bsequence-39-) を使うと、次のように書くことができます。

```haskell
validate :: Applicative f => Setting_ f -> f (Setting_ Identity)
validate = bsequence'

bsequence' :: (Applicative f, TraversableB b) => b f -> f (b Identity)
```

なんと、実装が 1 行で済んでしまいました。というか名前を変えて、型 `b` を `Setting_` に制限しただけです。

他にも barbies には様々な便利関数があるので、いろいろ探してみると良いでしょう。

## higgledy

HKD + barbies は便利そうですが、もう既に古き良きレコード型でプログラムを書いてしまったという人や、使う度に Identity をいちいち外すのは面倒という人も多いと思います。
そのような人も、 [higgledy](https://hackage.haskell.org/package/higgledy) を使えば後からでも HKD の恩恵を得ることができます。

いちばん最初に定義した、古き良きレコード型の `Setting` に立ち返ります。

```haskell
data Setting = Setting
    { verbose :: Bool
    , host :: String
    , port :: Int
    , timeout :: Int
    } deriving (Generic, Eq, Show)
```

higgledy を使うと、これの HKD 版がタダで手に入ります。

```haskell
type Setting_ = HKD Setting
```

この定義をするだけで、`Setting_` は `Show`, `Eq` をはじめ、`Monoid` や barbies の `FunctorB` や `TraversableB` などもタダで手に入ります。便利ですね。

一方で、値の構築や、フィールドのアクセスで Lens が必要など、若干面倒な部分もあります。

元の値から deconstruct する方法:

```haskell
ghci> setting = Setting True "example.com" 8080 300
ghci> deconstruct setting :: Setting_ Maybe
Setting {verbose = Just True, host = Just "example.com", port = Just 8080, timeout = Just 300}
```

build を使う方法:

```
ghci> build @Setting (Just True) (Just "example.com") (Just 8080) (Just 300)
```

`Setting_` から元の Setting へは、`construct` を使えば戻すことができます。

```haskell
ghci> construct example
Just (Setting {verbose = True, host = "example.com", timeout = 300})
```

フィールドのアクセスは Lens を使います。

```haskell
ghci> example = build @Setting (Just True) (Just "example.com") (Just 8080) (Just 300)
ghci> example ^. field @"timeout"
Just 300

ghci> example & field @"port" .~ Just 3000
Setting {verbose = Just True, host = Just "example.com", port = Just 3000, timeout = Just 300}
```

また、内部構造が Linked List 相当の物になるので、パフォーマンスが非常にシビアな場所で使うと問題を起こすかもしれません。
試しに GHCi で :print してみると……

```haskell
:print example
example = HKD (:*:
                 (:*: (Just True) (Just (: 'e' "xample.com")))
                 (:*:
                    (Just (ghc-prim-0.5.3:GHC.Types.I# 8080))
                    (Just (ghc-prim-0.5.3:GHC.Types.I# 300))))
```

しかし、これが実際に問題になる事は無いでしょう。というのも higgledy は元の古き良きレコード型に簡単に戻すことができるので、実際の処理では通常のレコード型を使えば良いからです。

## まとめ

さわりだけの紹介でしたが、Higher-Kinded Data を使うことで、従来のレコード型で書いていた様々なボイラープレートコードを排除し、自由度を飛躍的に高めることができました。

なお言語拡張

```
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
```

と

```
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveAnyClass #-}
```

の提供でお送りしました。