我能否获得构造函数列表(如“Typeable”、“Data”),但基于类型推断而不是我的类型的 运行 时间表示?

Can I get a list of constructors (like with `Typeable`, `Data`), but based on type inference instead of a run-time representation of my type?

我很难把问题放在一起。让我们逐步尝试:

我有一个 Haskell 类型 class(代表键盘布局 fyi):

class Data key => Palantype key where
  
  -- the instance has to provide this mapping
  keyCode :: key -> Char

  -- but the reverse is required, too:
  toKeys :: Char -> [key]

我可以基于 TypeableData:

toKeys 提供一个(效率不高的)默认实现

-- | override for efficiency
toKeys :: key -> Char -> [key]
toKeys k c =
  let t = dataTypeOf k
      ks = fromConstr . indexConstr t <$> [1..(maxConstrIndex t)]
      m = foldl (\m k -> Map.insertWith (++) (keyCode k) [k] m) Map.empty ks
  in  fromMaybe [] $ Map.lookup c m

... 上面的代码有效。不错

但是,有一个问题。默认实现需要 key 作为第一个参数,原因是:Data 需要类型的 运行 时间表示。这是由 DataType 使用 dataTypeOf :: a -> DataType.

提供的

我必须调整 toKeys 的类型签名并始终提供一个不太有意义的虚拟密钥。我知道这就是 Data.Data 的工作原理。但是有没有办法根据类型变量key获得同样的魔法?

Data.Typeable 中的函数 typeRep 似乎是这样工作的:

typeRep :: forall proxy a. Typeable a => proxy a -> TypeRep

但是 DatafromConstrindexConstrmaxConstrIndex)的所有工作都依赖于 运行 时间表示 DataType (出于某种原因?)。


使用Data.Proxied的优雅解决方案:

toKeys :: Char -> [key]
toKeys c =
  let t = dataTypeOfProxied (Proxy :: Proxy key)
      ks = fromConstr . indexConstr t <$> [1..(maxConstrIndex t)]
      m = foldl (\m k -> Map.insertWith (++) (keyCode k) [k] m) Map.empty ks
  in  fromMaybe [] $ Map.lookup c m

GHCi 中的快速测试:

> dataTypeOf (undefined :: Int)
DataType {tycon = "Prelude.Int", datarep = IntRep}

这表明 dataTypeOf 并不真正需要运行时值,第一个参数仅用于其类型。你可以(也应该)写类似

的东西
toKeys :: forall key . Data key => Char -> [key]
toKeys c =
   let t = dataTypeOf (undefined :: key)
   ...

在我看来,这个界面不是今天应该有的样子,但由于历史原因,我们仍然有它。当 Data 被设计时,我猜,我们没有 AllowAmbiguousTypes, TypeApplications 所以我们使用“未评估的”参数 and/or 代理。

如果今天设计Data,我想我们会有模棱两可的类型

dataTypeOf :: forall a . Data a => DataType

我们会将其用作 dataTypeOf @key