Hasパターンとは

## 基本的なアイデア

　HaskellにおけるHasパターンとは、レコード型を抽象化するためのパターンだ。

　下の型`Person`と、もう一つの型`Car`を考える。

```Haskell
data Person = Person
  { personName :: String
  , personAge  :: Int
  }

data Car = Car
  { carName :: String
  , carAge  :: Int
  }
```

　人や物の名前を使った関数書こうと思ったら、その関数を上のレコード型それぞれについて書く必要がある。

```Haskell
showPersonName :: Person -> IO ()
showPersonName Person{personName} = print personName

showCarName :: Car -> IO ()
showCarName Car{carName} = print carName
```

　ここで、`name`フィールドを持つことを、型クラスを用いて抽象化する。

```Haskell
class HasName a where
  name :: a -> String

instance HasName Person where
  name Person{personName} = personName

instance HasName Car where
  name Car{carName} = carName
```

　これで名前を持つ任意のレコードを受け取ることのできる関数を書けるようになった。

```Haskell
showName :: HasName a => a -> IO ()
showName a = print $ name a
```

　さらに、`age`フィールドを持つことを、同様に型クラスを用いて抽象化してみよう。

```Haskell
class HasAge a where
  age :: a -> Int

instance HasAge Person where
  age Person{personAge} = personAge

instance HasAge Car where
  age Car{carAge} = carAge
```

　各フィールドを抽象化することで、柔軟な関数を定義することができるようになる。

```Haskell
introduce :: (HasName a, HasAge a) => a -> IO ()
introduce a = print $ "Hello, my name is " <> name a <> " and I'm " <> show (age a) <> " years old."
```

　これがHasパターンの基本的なアイデアだ。

### フィールドをLensで表現する

　[基本的なアイデア](#基本的なアイデア)の章では、`Has*`型クラスに持たせたのはgetterだけだった。レコード型を抽象化するのであれば、setterも持たせるべきだろう。
　このようなところで役に立つのが`Lens`だ。`HasName`を下の通りに書き換える。

```Haskell
class HasName a where
  nameL :: Lens' a String

instance HasName Person where
  nameL = lens personName $ \x y -> x {personName = y}

instance HasName Car where
  nameL = lens carName $ \x y -> x {carName = y}
```

　そうすると、名前を書き換える処理を含む一般的な関数を書けるようになる。もちろん、合成といったLensの特徴も享受することができる。

```Haskell
rename :: HasName a => String -> a -> a
rename name a = a & nameL .~ name
```

　この章で紹介したコードは、こちらで動作を確認することができる。

https://ideone.com/3UdFeF

## Hasパターンの使用例

### 同じ名前のフィールドを持つ複数のレコードに共通のアクセサを提供する

　上で紹介した通り、Hasパターンによるレコードの抽象化は、フィールドへのアクセサを型クラスで抽出することによって実現される。
　Haskellはレコードのサポートが貧弱なため、これがそのまま役に立つ場合がある。

```Haskell
data Person = Person
  { personName :: String
  , personAge  :: Int
  }

data Car = Car
  { carName :: String
  , carAge  :: Int
  }

class HasName a where
    nameL :: Lens' a String

instance HasName Person where
    nameL = lens personName $ \x y -> x {personName = y}

instance HasName Car where
    nameL = lens carName $ \x y -> x {carName = y}

printName :: (HasName a) => a -> IO ()
printName a = print $ a ^. nameL
```

　尤も、この使い方をする場合、`DuplicateRecordFields`と後で紹介する[generic-lens](https://hackage.haskell.org/package/generic-lens) が提供する`HasField`を使う方がよさそうだ。

```Haskell
{-# LANGUAGE DuplicateRecordFields #-}

import Data.Generics.Product.Fields

data Person = Person
  { name :: String
  , age  :: Int
  } deriving (Generic)

data Car = Car
  { name :: String
  , age  :: Int
  } deriving (Generic)

printName :: (HasField' "name" a String) => a -> IO ()
printName a = print $ a ^. (field' @"name")
```

### 巨大なレコードへの依存を抽出する

　Hasパターンは、巨大なレコードの一部のフィールドに依存する関数やアクションの依存を最低限に抑えるのに役に立つ。
　例えば環境変数の集合を表現しようと思ったら、`ReaderT`モナドをネストするか、巨大なレコード型を作ることになるだろう。どちらを採用するにしても、ある環境変数に依存するアクションがあったとき、その使用する環境変数にのみ依存させ、環境変数全体に依存することは避けたいはずだ。
　ここでHasパターンが役に立つ。

　よくある実用的なアプリがあるとする。
　そのアプリは`MonadReader`から環境を引ける文脈を持っていて、環境は必要に応じて`Has*`型クラスを実装している。

```Haskell
-- The whole environment would be like:
data Env = Env
  { envPort      :: Int
  , envLandScape :: String
  -- Other many fields
  }

-- And your program runs on a monad like:
type MyAppT m a = ReaderT Env m a

class HasPort a where
  envPortL :: Lens' a Int

instance HasPort Env where
  envPortL = lens envPort $ \x y -> x {envPort = y}

-- (Has* boilerplates for each field follow here)
```

　すると、環境の一部を使うアクションを次のように表現できる。このアクションは最低限の依存しか持たないため、環境や文脈の実装の変更に耐えることができる。

```Haskell
logPort :: (MonadReader env m, HasPort env, MonadIO m) => m ()
logPort = view envPortL >>= liftIO . print
```

　当然、アクションは何も意識することなく使うことができる。

```Haskell
appMain :: MyAppT IO ()
appMain = do
  logPort  -- Prints envPort on stdout.
```

　この章で紹介したコードは、こちらで動作を確認することができる。

https://ideone.com/CDDVAf

### DIを表現する

　もう一つのHasパターンの使用例は、DIを表現することだ。
　Haskellでは、レコード型を使ってオブジェクト指向でいうインターフェースを表現することができる。（参考: [Java interfaces map to Haskell records](https://chris-martin.org/2017/interfaces-and-records)）

　例えば、`logInfo`メソッドを持つ`Logger`インターフェースは下のように定義できる。

```Haskell
data Logger = Logger
  { logInfo :: String -> IO ()
  }

class HasLogInfo a where
  logInfoL :: Lens' a (String -> IO ())

instance HasLogInfo Logger where
  logInfoL = lens logInfo $ \x y -> x {logInfo = y}
```

　実装クラスに当たるものは、下のようなシンプルな値だ。

```Haskell
loggerImpl :: Logger
loggerImpl = Logger putStrLn
```

　この`Logger`の実装をアプリの環境に持たせよう。

```Haskell
data Env = Env
  { envPort      :: Int
  , envLandScape :: String
  , envLogger    :: Logger
  -- Other many fields
  }

-- And your program runs on a monad like:
type MyAppT m a = ReaderT Env m a

class HasLogger a where
  loggerL :: Lens' a Logger

instance HasLogger Env where
  loggerL = lens envLogger $ \x y -> x {envLogger = y}
```

　そうすると、他の環境変数と同じようにして環境から実装クラスを引っ張ってこられるようになる。もちろん、実装クラスのメソッドを呼び出すことも可能だ。

```Haskell
logInfo' :: (MonadIO m, MonadReader env m, HasLogger env) => String -> m ()
logInfo' msg = do
  logger <- view loggerL
  liftIO $ logger ^. logInfoL $ msg

appMain :: ReaderT Env IO ()
appMain = do
  logInfo' "Something"
```

　依存オブジェクトは、main関数でアプリを実行する時に注入することになる。

```Haskell
main :: IO ()
main = runReaderT appMain (Env 8080 "dev" loggerImpl)
```

　この章で紹介したコードは、こちらで動作を確認することができる。

https://ideone.com/1Pzfo6

## ボイラープレートを減らす試み

　さて、ここまででHasパターンというものを何となく理解してもらえたと思う。
　しかしフィールドを一つ一つ型クラスにする都合で、Hasパターンは必要とするボイラープレートも多い。
　そこでこの章では、Hasパターンからボイラープレートをなくそうとする試みを紹介しようと思う。

### generic-lens

　[generic-lens](http://hackage.haskell.org/package/generic-lens) は、`HasField`型クラスを提供する。これは、Genericを使って各フィールドの抽象化を自動的にやってくれるものだ。

　前準備は、レコードに`Generic`を導出させておくだけでいい。

```Haskell
import Data.Generics.Product.Fields

data Env = Env
    { landscape :: String
    } deriving (Generic)

data AnotherEnv = AnotherEnv
    { landscape :: String
    } deriving (Generic)
```

　これだけで、抽象化したフィールドに依存する関数を書くことができるようになる。

```Haskell
printLandscape :: HasField' "landscape" a String => a -> IO ()
printLandscape a = print $ a ^. (field' @"landscape")
```

　`Has*`の定義とインスタンス化を全くやらなくてよくなり、記述がかなり簡潔になる。
　ただし、`HasField`を使って制約を書けるのは、そのレコード型がまさに持つフィールドだけだ。ネストしたフィールドにアクセスするには、Lensをネストさせてアクセスするしかなく、この点で`Has*`を手書きする場合に比べて自由度に劣る。

　例えば、下のような`Env`と、`Env`のフィールド`ApplicationConfig`があったとする。

```Haskell
data Env = Env
  { config :: ApplicationConfig
  } deriving (Generic)

data ApplicationConfig = ApplicationConfig
  { landscape :: String
  , port      :: Int
  } deriving (Generic)

type MyAppT m a = ReaderT Env m a
```

　この時、`landscape`のみに依存する`MyAppT`のアクションを書こうと思ったら、次のように制約を書くしかない。

```Haskell
logLandscape :: (MonadIO m, MonadReader env m, HasField' "config" env ApplicationConfig) => m ()
logLandscape = do
    landscape <- view (field' @"config" . field' @"landscape")
    liftIO . print $ "Hello, " <> landscape
```

　しかしこれでは`logLandscape`が`port`に対する依存も抱えることになり、必要最低限の依存というわけにはいかなくなってしまう。

　`HasField`型クラスは、重複したレコード名をうまく扱ったり、同名のフィールドを持つレコードを抽象化しようとしたときに威力を発揮することになるだろう。
　何といっても`Generic`さえ実装していれば使えるのだから、`Has*`みたいな大げさなものを書きたくない場合には手軽で便利だ。

### data-has

　[data-has](https://hackage.haskell.org/package/data-has-0.3.0.0) は、`Has`型クラスを提供する。`Has`型クラスは、今まで一つ一つ定義していた`Has*`を一般化した型クラスだ。
　`Has`型クラスの実装は自分で行う必要がある。

```Haskell
import Data.Has

newtype Env = Env
    { _envConfig  :: ApplicationConfig
    }

data ApplicationConfig = ApplicationConfig
    { _acLandscape :: !Landscape
    , _acPort      :: !Port
    }

newtype Landscape = Landscape String

newtype Port = Port Int

instance Has ApplicationConfig Env where
    hasLens = lens _envConfig $ \x y -> x {_envConfig = y}

instance Has Landscape ApplicationConfig where
    hasLens = lens _acLandscape $ \x y -> x {_acLandscape = y}

instance Has Port ApplicationConfig where
    hasLens = lens _acPort $ \x y -> x {_acPort = y}

instance Has Landscape Env where
    hasLens = (hasLens :: Lens' Env ApplicationConfig) . (hasLens :: Lens' ApplicationConfig Landscape)

instance Has Port Env where
    hasLens = (hasLens :: Lens' Env ApplicationConfig) . (hasLens :: Lens' ApplicationConfig Port)
```

　一般化された`Has`型クラスはフィールドを型で区別しているため、プリミティブな型のフィールドは使いづらくなっている。
　`MyAppT`のアクションは次のような感じで書ける。依存を最小限に抑えられるほか、`Has*`を手書きしていた時に比べてインターフェースが統一されて読みやすくなっている。

```Haskell
printServerInfo :: (MonadIO m, MonadReader env m, Has Landscape env, Has Port env) => m ()
printServerInfo = do
    Landscape landscape <- view hasLens
    Port port <- view hasLens
    liftIO . print $ "Landscape: " <> landscape
    liftIO . print $ "Port: " <> show port
```

### makeFields

　[lens](https://hackage.haskell.org/package/lens) や [microlens-th](http://hackage.haskell.org/package/microlens-th) で公開されている`makeFields`は、手書きしていた`Has*`型クラス群をTemplateHaskellを用いてゴリゴリに生成してくれる。

```Haskell
{-# LANGUAGE TemplateHaskell #-}

import Lens.Micro.TH

newtype Env = Env
    { _envConfigL  :: ApplicationConfig
    }

data ApplicationConfig = ApplicationConfig
    { _applicationConfigLandscapeL :: !String
    , _applicationConfigPortL      :: !Int
    }

makeFields ''Env
makeFields ''ApplicationConfig

instance HasLandscapeL Env String where
    landscapeL = configL . landscapeL

instance HasPortL Env Int where
    portL = configL . portL
```

　見ての通り、`makeFields`は指定したレコードのフィールド全ての`Has*`クラスの生成と実装を行う。
　レコードの定義が独自の命名規則（`_{レコード名}{フィールド名}`）に従う必要があるが、最も単純で強力な方法と言えるだろう。
　`AppT`のアクションの定義も、自分で`Has*`を定義したときとほとんど同じだ。

```Haskell
printServerInfo :: (MonadIO m, MonadReader env m, HasLandscapeL env String, HasPortL env Int) => m ()
printServerInfo = do
    landscape <- view landscapeL
    port <- view portL
    liftIO . print $ "Landscape: " <> landscape
    liftIO . print $ "Port: " <> show port
```

## おわりに

　HaskellにおけるHasパターンが、レコード型を抽象化する強力な方法であることを説明した。
　また、Hasパターンのボイラープレートを減らすことを目的としたライブラリをいくつか紹介した。ライブラリは物によって達成したい目的が微妙に異なり、どれを使うかはHasパターンをどのような目的で使うかによって変わってくるだろう。