0

作为我需要从一元计算记录数据的程序的一部分,我正在尝试定义一个类以使其更方便。

module Serial where
import Data.Int
import Data.IORef
import System.IO
import Control.Monad.Trans
import Foreign.Ptr
import Foreign.Marshal
import Foreign.Storable

class MonadIO m => Serial m a where
    get :: Handle -> m a
    put :: Handle -> a -> m ()

我想做的一件事是在“更高”的 monad 中定义get和,因为某些数据在. 对于更简单的数据,例如 的实例,就足够了。我想将基本实例保留在“最低”可能的单子中,但允许将操作提升到任何“更高”实例。putIOStorableIOMonadIO

instance (Serial m a, MonadIO (t m), MonadTrans t) 
    => Serial (t m) a where
       get = lift . get
       put h = lift . put h

instance Storable a => Serial IO a where
    get h = alloca (\ptr 
        -> hGetBuf h ptr (sizeOf (undefined :: a))
        >> peek ptr)
    put h a = with a (\ptr 
        -> hPutBuf h ptr $ sizeOf a)

这个想法是启用功能,如

func :: Serial m a => Handle -> a -> m ()
func h a = put h (0::Int32) >> put h a

其中 in 中的实例IO可以与 any 中的实例组合MonadIO。但是,使用我当前的代码,GHC 无法推断Serial m Int32. 对于解除IO此问题的特定情况,可以通过 解决liftIO,但如果基本类型t IO不再起作用。我认为这可以通过重叠实例来解决,但如果可能的话,我想避免这种情况。有什么办法可以做到这一点?

4

1 回答 1

3

您可以只写出所需的额外约束:

func :: (Serial m a, Serial m Int32) => Handle -> a -> m ()
func h a = put h (0::Int32) >> put h a

(我认为这需要-XFlexibleContexts。)

如果这使签名变得笨拙,您可以将约束组合在“约束同义词类”中:

class (Serial m a, Serial m Int32, Serial m Int64, ...)
       => StoSerial m a
instance (Serial m a, Serial m Int32, Serial m Int64, ...)
       => StoSerial m a

func :: StoSerial m a => Handle -> a -> m ()
func h a = put h (0::Int32) >> put h a
于 2020-10-03T12:34:14.597 回答