4

アプリケーションを構成するとき、多くの場合、そのフィールドを定義する方法は、フィールドを使用する方法と同じです。

data CfgMyHostName = CfgMyHostName Text

また、異なる場合もあります。これを型クラスで正式にしましょう:

data UsagePhase = ConfigTime | RunTime -- Used for promotion to types

class Config (a :: UsagePhase -> *) where
  type Phase (p :: UsagePhase) a = r | r -> a
  toRunTime :: Phase ConfigTime a -> IO (Phase RunTime a)

data DatabaseConfig (p :: UsagePhase)

instance Config DatabaseConfig where
  type Phase ConfigTime DatabaseConfig = ConnectInfo
  type Phase RunTime    DatabaseConfig = ConnectionPool
  toRunTime = connect

典型的なサービス構成には多くのフィールドがあり、各カテゴリにいくつかあります。一緒に構成する小さなコンポーネントをパラメーター化すると、大きな複合レコードを 2 回ではなく 1 回 (構成仕様用に 1 回、実行時データ用に 1 回) 書き込むことができます。これは、「Trees that Grow」という論文の考え方に似ています。

data UiServerConfig (p :: UsagePhase) = CfgUiServerC {
  userDatabase  :: Phase p DatabaseConfig
  cmsDatabase   :: Phase p DatabaseConfig
  ...
  kinesisStream :: Phase p KinesisConfig
  myHostName    :: CfgMyHostName 
  myPort        :: Int
}

UiServerConfigは、構成したい多くのサービスの 1 つなので、そのようなレコード タイプを派生させ、クラスにデフォルトの実装をGeneric追加するとよいでしょう。ここで行き詰まります。toRunTimeConfig

のようにパラメータ化された型が与えられた場合、すべてのレコード フィールドに (再帰的に)影響するdata Foo f = Foo { foo :: TypeFn f Int, bar :: String}ような任意の型のトラバーサルを一般的に導出するにはどうすればよいですか?FooTypeFn

私の混乱の一例として、私はジェネリック SOP を次のように使用しようとしました。

gToRunTime :: (Generic a, All2 Config xs)
           => Phase ConfigTime xs
           -> IO (Phase RunTime xs)
gToRunTime = undefined

これは が原因xs :: [[*]]で失敗しますConfigが、kind の型引数を取りますa :: ConfigPhase -> *

もつれを解くために何を読むべきかについてのヒントは本当にありがたいです. 完全なソリューションも受け入れられます:)

4

1 に答える 1