Haskell如何让所有Storable类型自动成为ManagedValue类型类实例
问题原因说明
你遇到的报错是GHC的实例终止检查机制导致的:为了避免实例搜索过程中出现无限递归,GHC默认要求实例约束的"大小"必须小于实例头的大小,你编写的instance Storable a => ManagedValue a中,约束和实例头的类型参数都是a,大小相等,因此触发了这个报错。
以下是三种可行的解决方案,按推荐优先级排序:
方案1:使用默认签名(最安全,无重叠风险)
这个方案不需要开启不安全的编译器扩展,也不会和自定义的非Storable类型的ManagedValue实例冲突,是最推荐的做法。
你可以借助DefaultSignatures扩展为ManagedValue类型类的所有方法和关联类型提供默认实现,仅当类型属于Storable实例时可以直接复用默认实现:
- 首先开启需要的扩展:
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE DefaultSignatures #-}
- 修改类型类定义,添加默认实现:
class ManagedValue a where type ManagedPtr a = (r :: *) | r -> a -- 关联类型默认实现 type ManagedPtr a = Ptr a withManaged :: a -> (ManagedPtr a -> IO b) -> IO b -- Storable 类型的默认方法实现 default withManaged :: Storable a => a -> (Ptr a -> IO b) -> IO b withManaged v f = do fp <- mallocForeignPtr withForeignPtr fp $ \p -> do poke p v f p getManaged :: ManagedPtr a -> IO a default getManaged :: Storable a => Ptr a -> IO a getManaged = peek castManagedToPtr :: ManagedPtr a -> Ptr b default castManagedToPtr :: Storable a => Ptr a -> Ptr b castManagedToPtr = castPtr castPtrToManaged :: Ptr b -> ManagedPtr a default castPtrToManaged :: Storable a => Ptr b -> Ptr a castPtrToManaged = castPtr
- 为
Storable类型声明ManagedValue实例时,只需要写空实例声明即可,不需要重复实现:
instance ManagedValue Int instance ManagedValue Double instance ManagedValue Word8 -- 其他需要的Storable类型都可以按这个格式一行声明
如果需要批量生成常用类型的实例,也可以借助模板Haskell写一个简单的生成函数,几行代码就能批量生成所有需要的实例,不需要手动逐个编写。
方案2:开启UndecidableInstances扩展(最省事)
如果你确定不会出现实例循环依赖的情况,只需要开启UndecidableInstances扩展就能直接使用你原本写的通用实例,这个扩展只是关闭了GHC的实例终止检查,在你这个场景下是完全安全的,不会有运行时风险,很多基础库也会使用这个扩展。
- 开启扩展:
{-# LANGUAGE UndecidableInstances #-}
- 你原本编写的通用实例就可以正常编译运行,所有
Storable类型会自动获得ManagedValue实例。
⚠️ 注意:这个方案的唯一问题是,如果你之后要为非Storable的类型自定义ManagedValue实例,会出现实例重叠的报错,因为通用实例覆盖了所有类型的ManagedValue实例,不管是不是Storable的,所以只适合不需要自定义非Storable实例的场景使用。
方案3:模板Haskell批量生成实例
如果你既不想开UndecidableInstances,也不想手动写空实例声明,可以用模板Haskell批量生成所有常用Storable类型的实例,代码示例如下:
-- 定义TH生成函数 import Language.Haskell.TH deriveStorableManagedValue :: Name -> Q [Dec] deriveStorableManagedValue t = [d| instance ManagedValue $(conT t) |] -- 批量生成实例 mapM_ deriveStorableManagedValue [''Int, ''Double, ''Word8, ''Word16, ''Word32, ''Word64, ''Float]
这个方案兼具安全性和便捷性,不需要手动编写重复的实例声明,也不会有实例重叠的问题。
内容的提问来源于stack exchange,提问作者Z-Y.L
相关产品推荐
相关产品推荐

