Haskell类型转字符串:如何通过类型应用实现converter?
解决方案:封装
Typeable实现类型名称字符串获取 你想要的是仅通过类型参数就能直接获取对应类型的字符串表示,不需要传入实际值或手动处理Proxy的冗余信息,这个需求可以通过结合Typeable类型类和GHC的类型应用扩展优雅实现。
方案1:封装成通用类型类(推荐)
我们可以定义一个自定义类型类,把Typeable的类型表示逻辑封装起来,让用户只需要通过类型应用调用即可:
{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-} import Data.Typeable (Typeable, typeRep) import Data.Proxy (Proxy(..)) -- 定义类型类,用于获取类型的字符串名称 class TypeName a where typeName :: String -- 为所有Typeable的类型自动推导实例 instance Typeable a => TypeName a where typeName = show $ typeRep (Proxy @a)
使用方式非常简洁,直接通过类型应用指定目标类型:
-- 输出 "Int" main = print $ typeName @Int -- 输出 "[Char]" main = print $ typeName @String
这个方案的优势在于:
- 完全屏蔽了
Proxy的细节,用户不用关心内部如何传递类型信息 - 大部分标准库类型都自带
Typeable实例,无需手动编写 - 自动处理复杂类型(比如列表、元组)的字符串表示
方案2:更高效的无Proxy版本
如果追求运行时效率,可以使用Proxy#(GHC内部的无装箱类型代理)来避免创建Proxy值,进一步优化:
{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UnboxedTuples #-} import GHC.Exts (Proxy#) import Data.Typeable (typeRep#) import GHC.Show (showTypeRep) typeName :: forall a. Typeable a => String typeName = showTypeRep (typeRep# (Proxy# :: Proxy# a))
使用方式和上面完全一致,但内部不会产生额外的Proxy运行时值,性能更优。
自定义类型名称(可选)
如果你对TypeRep默认的字符串格式不满意(比如想把[] Int改成[Int]),可以手动为特定类型编写实例:
instance TypeName Bool where typeName = "Bool" instance TypeName a => TypeName [a] where typeName = "[" ++ typeName @a ++ "]" -- 此时 typeName @[Int] 会输出 "[Int]" 而不是 "[] Int"
为什么这比你之前的方案更好?
你之前尝试用Proxy但需要移除字符串中的Proxy,是因为直接show Proxy @Int会输出Proxy Int,而我们的方案是通过typeRep获取目标类型本身的表示,完全不会包含Proxy的信息,从根源上避免了字符串处理的麻烦,既优雅又不易出错。
内容的提问来源于stack exchange,提问作者Reygoch
相关产品推荐
相关产品推荐

