代码之家  ›  专栏  ›  技术社区  ›  Z-Y.L

如何将特设多态类型声明为特定类型类的实例?

  •  0
  • Z-Y.L  · 技术社区  · 3 年前

    我有一个名为的类型类 ManagedValue 定义如下:

    class ManagedValue a where
        type ManagedPtr a = (r :: *) | r -> a
        withManaged :: a -> (ManagedPtr a -> IO b) -> IO b
        getManaged :: ManagedPtr a -> IO a
        castManagedToPtr :: ManagedPtr a -> Ptr b
        castPtrToManaged :: Ptr b -> ManagedPtr a
    

    对于作为实例的每个类型 Storable typeclass,它也是的一个实例 管理的价值 typeclass,但不是全部 管理的价值 s是可存储的。然而,我不能把它定义为 instance Storable a => ManagedPtr a where ... 因为 GHC 给出错误 The constraint ‘Storable a’ is no smaller than the instance head ‘ManagedValue a’ .

    我知道我可以将类型类层次结构定义为 Num Intergral 喜欢 class (Storable a, ManagedValue a) => StorableValue a where ... 。但是这种方式需要手动定义所有实例,这很麻烦。

    我想宣布的是 可存储 值也是 管理的价值 这样我就可以在一个地方定义一个实例,并获得的实现 Int , Double 等等,类似于:

    -- This definition doesn't work
    instance Storable a => ManagedValue a where
      type ManagedPtr a = Ptr a
    
      withManaged v f = do
        fp <- mallocForeignPtr
        withForeignPtr fp $ \p -> do
           poke p v
           f p
    
      getManaged p = peek p
    
      castManagedToPtr = castPtr
      castPtrToManaged = castPtr
    

    然后我可以实现的方法 管理的价值 对于 Int , 双重的

    谢谢你的提示!

    0 回复  |  直到 3 年前
        1
  •  2
  •   Joseph Sible-Reinstate Monica    3 年前

    您正在寻找 default method signatures :

    {-# LANGUAGE DefaultSignatures, TypeFamilyDependencies #-}
    
    import Foreign.ForeignPtr
    import Foreign.Ptr
    import Foreign.Storable
    
    class ManagedValue a where
        type ManagedPtr a = (r :: *) | r -> a
        type ManagedPtr a = Ptr a
    
        withManaged :: a -> (ManagedPtr a -> IO b) -> IO b
        default withManaged :: (Storable a, ManagedPtr a ~ Ptr a) => a -> (ManagedPtr 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, ManagedPtr a ~ Ptr a) => ManagedPtr a -> IO a
        getManaged p = peek p
    
        castManagedToPtr :: ManagedPtr a -> Ptr b
        default castManagedToPtr :: ManagedPtr a ~ Ptr a => ManagedPtr a -> Ptr b
        castManagedToPtr = castPtr
    
        castPtrToManaged :: Ptr b -> ManagedPtr a
        default castPtrToManaged :: ManagedPtr a ~ Ptr a => Ptr b -> ManagedPtr a
        castPtrToManaged = castPtr
    

    你还是得写 instance ManagedValue Int , instance ManagedValue Double ,等等,但您不必在这些实例中实现任何内容。


    还有一个选项不需要您手动编写 instance s代表的是 Storable ,但它也有自己的一些注意事项:

    {-# LANGUAGE TypeFamilies #-}
    
    import Foreign.ForeignPtr
    import Foreign.Ptr
    import Foreign.Storable
    
    class ManagedPtr r where
        type ManagedValue r
        withManaged :: ManagedValue r -> (r -> IO b) -> IO b
        getManaged :: r -> IO (ManagedValue r)
        castManagedToPtr :: r -> Ptr b
        castPtrToManaged :: Ptr b -> r
    
    instance Storable a => ManagedPtr (Ptr a) where
      type ManagedValue (Ptr a) = a
    
      withManaged v f = do
        fp <- mallocForeignPtr
        withForeignPtr fp $ \p -> do
           poke p v
           f p
    
      getManaged p = peek p
    
      castManagedToPtr = castPtr
      castPtrToManaged = castPtr
    

    我基本上把typeclass从内向外翻转,把typeclass放在指针类型上,并让值成为一个关联的类型。主要的警告是,值类型与指针类型之间不再存在依赖关系,因此当您实际使用此类型类时,可能会有大量不明确的类型需要解决 TypeApplications 或者什么的。第二个警告是 ManagedPtr 只能是 Ptr 现在如果基础价值是 可存储 .