bug 在当前的 GHC 中打破了这一点。一旦修复被合并,这应该可以正常工作。与此同时,另一个答案可以让你渡过难关。
首先,定义
data Elem :: k -> [k] -> Type where
Here :: Elem x (x : xs)
There :: Elem x xs -> Elem x (y : xs)
Elem x xs 告诉您在哪里可以找到xs 中的x。此外,这是一个存在包装器:
data EntryOfVal v kvs = forall k. EntryOfVal (Elem (k, v) kvs)
-- to be clear, this is the type constructor (,) :: Type -> Type -> Type
type family EntryOfValKey (eov :: EntryOfVal v kvs) :: Type where
EntryOfValKey ('EntryOfVal (_ :: Elem (k, v) kvs)) = k
type family GetEntryOfVal (eov :: EntryOfVal v kvs) :: Elem (EntryOfValKey eov, v) kvs where
GetEntryOfVal ('EntryOfVal e) = e
如果你在类型级别有一个Elem,你可以实现它
class MElem (e :: Elem (x :: k) xs) where
mElem :: Elem x xs
instance MElem Here where
mElem = Here
instance MElem e => MElem (There e) where
mElem = There (mElem @_ @_ @_ @e)
同样,您可以实现EntryOfVal
type MEntryOfVal eov = MElem (GetEntryOfVal eov) -- can be a proper constraint synonym
mEntryOfVal :: forall v kvs (eov :: EntryOfVal v kvs).
MEntryOfVal eov =>
EntryOfVal v kvs
mEntryOfVal = EntryOfVal (mElem @_ @_ @_ @(GetEntryOfVal eov))
如果一个类型是类型列表中的一个元素,那么您可以从该类型列表的HList 中提取该类型的值:
indexH :: Elem t ts -> HList ts -> t
indexH Here (HCons x _) = x
indexH (There i) (HCons _ xs) = indexH i xs
(我觉得有必要指出indexH 对HList 的根本重要性。一方面,HList ts 与其索引器forall t. Elem t ts -> t 同构。另外,indexH 有一个对偶,injS :: Elem t ts -> t -> Sum ts适合Sum。)
同时,在类型级别上,这个函数可以给你第一个可能的EntryOfVal给定一个值类型和一个列表:
type family FirstEntryOfVal (v :: Type) (kvs :: [Type]) :: EntryOfVal v kvs where
FirstEntryOfVal v ((k, v) : _) = 'EntryOfVal Here
FirstEntryOfVal v (_ : kvs) = 'EntryOfVal (There (GetEntryOfVal (FirstEntryOfVal v kvs)))
将具体化类与FirstEntryOfVal 分开的原因是因为这些类是可重用的。您可以轻松编写返回 Elems 或 EntryOfVals 的新类型族并将它们具体化。将它们合并到一个整体类中很麻烦,现在您必须每次都重写MElem 的“逻辑”(不是很多),而不是重复使用它。然而,我的方法确实会带来更高的前期成本。但是,所需的代码完全是机械的,因此可以想象一个 TH 库可以为您编写它。我不知道有哪个库可以 处理这个问题,但singletons 计划这样做。
现在,这个函数可以根据EntryOfVal 证明为您提供一个值:
indexHVal :: forall v kvs. EntryOfVal v kvs -> HList kvs -> v
indexHVal (EntryOfVal e) = snd . indexH e
现在 GHC 可以为您做一些思考:
indexHFirstVal :: forall v kvs. MEntryOfVal (FirstEntryOfVal v kvs) =>
HList kvs -> v
indexHFirstVal = indexHVal (mEntryOfVal @_ @_ @(FirstEntryOfVal v kvs))
一旦你有了价值,你就需要找到钥匙。出于效率考虑(我认为 O(n) 与 O(n^2))原因和出于理智考虑,我们不会制作 EntryOfVal 的镜像,而是使用稍微不同的类型。现在我只给出样板文件而不做解释
-- for maximal reuse:
-- data All :: (k -> Type) -> [k] -> Type
-- where an All f xs contains an f x for every x in xs
-- plus a suitable data type to recover EntriesOfKey from All
-- not done here mostly because All f xs's materialization
-- depends on f's, so we'd need more machinery to generically
-- do that
-- in an environment where the infrastructure already exists
-- (e.g. in singletons, where our materializers decompose as a
-- composition of SingI materialization and SingKind demotion)
-- using All would be feasible
data EntriesOfKey :: Type -> [Type] -> Type where
Nowhere :: EntriesOfKey k '[]
HereAndThere :: EntriesOfKey k kvs -> EntriesOfKey k ((k, v) : kvs)
JustThere :: EntriesOfKey k kvs -> EntriesOfKey k (kv : kvs)
class MEntriesOfKey (esk :: EntriesOfKey k kvs) where
mEntriesOfKey :: EntriesOfKey k kvs
instance MEntriesOfKey Nowhere where
mEntriesOfKey = Nowhere
instance MEntriesOfKey e => MEntriesOfKey (HereAndThere e) where
mEntriesOfKey = HereAndThere (mEntriesOfKey @_ @_ @e)
instance MEntriesOfKey e => MEntriesOfKey (JustThere e) where
mEntriesOfKey = JustThere (mEntriesOfKey @_ @_ @e)
逻辑:
type family AllEntriesOfKey (k :: Type) (kvs :: [Type]) :: EntriesOfKey k kvs where
AllEntriesOfKey _ '[] = Nowhere
AllEntriesOfKey k ((k, _) : kvs) = HereAndThere (AllEntriesOfKey k kvs)
AllEntriesOfKey k (_ : kvs) = JustThere (AllEntriesOfKey k kvs)
实际值操作
updateHKeys :: EntriesOfKey k kvs -> (k -> k) -> HList kvs -> HList kvs
updateHKeys Nowhere f HNil = HNil
updateHKeys (HereAndThere is) f (HCons (k, v) kvs) = HCons (f k, v) (updateHKeys is f kvs)
updateHKeys (JustThere is) f (HCons kv kvs) = HCons kv (updateHKeys is f kvs)
让 GHC 多想想
updateHAllKeys :: forall k kvs. MEntriesOfKey (AllEntriesOfKey k kvs) =>
(k -> k) -> HList kvs -> HList kvs
updateHAllKeys = updateHKeys (mEntriesOfKey @_ @_ @(AllEntriesOfKey k kvs))
现在大家一起:
replaceAll :: forall t kvs.
(MEntryOfVal (FirstEntryOfVal t kvs), MEntriesOfKey (AllEntriesOfKey t kvs)) =>
HList kvs -> HList kvs
replaceAll xs = updateHAllKeys (const $ indexHFirstVal @t xs) xs