アプリケーションを構成するとき、多くの場合、そのフィールドを定義する方法は、フィールドを使用する方法と同じです。
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
追加するとよいでしょう。ここで行き詰まります。toRunTime
Config
のようにパラメータ化された型が与えられた場合、すべてのレコード フィールドに (再帰的に)影響するdata Foo f = Foo { foo :: TypeFn f Int, bar :: String}
ような任意の型のトラバーサルを一般的に導出するにはどうすればよいですか?Foo
TypeFn
私の混乱の一例として、私はジェネリック SOP を次のように使用しようとしました。
gToRunTime :: (Generic a, All2 Config xs)
=> Phase ConfigTime xs
-> IO (Phase RunTime xs)
gToRunTime = undefined
これは が原因xs :: [[*]]
で失敗しますConfig
が、kind の型引数を取りますa :: ConfigPhase -> *
もつれを解くために何を読むべきかについてのヒントは本当にありがたいです. 完全なソリューションも受け入れられます:)