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

How to declare an ad hoc polymorphic type be an instance of an specific type class?

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

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类型class实例的类型,它也是ManagedValue类型class的实例,但不是所有ManagedValue 是可存储的。但是,我不能将它定义为 instance Storable a => ManagedPtr a where ... 之类的东西,因为 GHC 给出错误 The constraint ‘Storable a’ is no smaller than the instance head ‘ManagedValue a’.

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

我想要的是声明所有 Storable 值也是 ManagedValue 这样我就可以在一个地方定义一个实例并获得 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

然后我可以为IntDouble

实现ManagedValue的方法

感谢任何提示!

您正在寻找 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 Intinstance ManagedValue Double 等,但您不必在这些实例中实现任何内容。


还有另一个选项不需要您为 Storable 的内容手动编写任何 instance,但它有一些自己的注意事项:

{-# 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

我基本上把类型类从里到外翻转过来,将类型类放在指针类型上,并将值作为关联类型。主要注意事项不再是值类型到指针类型的依赖关系,因此当您实际使用此类型类时,您可能会遇到大量需要使用 TypeApplications 或其他方式解决的歧义类型。第二个警告是 ManagedPtr 现在只能是 Ptr 如果基础值为 Storable.