我能否获得构造函数列表(如“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]
我可以基于 Typeable
和 Data
:
为 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
但是 Data
(fromConstr
、indexConstr
、maxConstrIndex
)的所有工作都依赖于 运行 时间表示 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
。
我很难把问题放在一起。让我们逐步尝试:
我有一个 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]
我可以基于 Typeable
和 Data
:
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
但是 Data
(fromConstr
、indexConstr
、maxConstrIndex
)的所有工作都依赖于 运行 时间表示 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
。