问题描述
作为我需要记录单子计算数据的程序的一部分,我试图定义一个类以使其更加方便。
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
和put
,因为在IO
中无法访问某些数据。对于更简单的数据,例如Storable
的实例,IO
就足够了。我想将基本实例保留在“最低”的monad中,但允许将操作提升到任何“较高” Monadio
实例。
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
其中IO
中的实例可以与任何Monadio
中的实例合并。但是,按照我当前的代码,GHC无法推断出Serial m Int32
的实例。对于提升IO
的特殊情况,此问题可以通过liftIO
来解决,但是如果基本类型为t IO
则不再起作用。我认为可以通过重叠实例来解决此问题,但我想尽可能避免这种情况。有什么办法可以做到这一点?
解决方法
您只需写出所需的额外约束:
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,...)
=> StoSerial m a
func :: StoSerial m a => Handle -> a -> m ()
func h a = put h (0::Int32) >> put h a