如何为GADT类型编写通用函数批量发射MsgPkt?
解决方案:适配MsgPkt的泛型元组处理类调整
首先需要调整Gsendme1类的定义,引入关联类型明确输入与输出的类型映射,同时约束每个元素都是MsgPkt m 'AskMD(请求方向),对应输出为MsgPkt m 'AnsMD(响应方向)。以下是完整实现:
1. 启用必要语言扩展
在文件顶部添加以下扩展声明:
{-# LANGUAGE GADTs, TypeFamilies, FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
2. 调整后的Gsendme1类定义
用关联类型OutType绑定输入类型对应的输出类型,方法gsendme1复用launch的参数与IO结果结构:
class Gsendme1 input where type OutType input gsendme1 :: Timeout Microsec -> IPv4 -> PortNumber -> input -> IO (Either LaunchError (OutType input))
3. 核心实例实现
空元组实例
处理空输入的边界情况:
instance Gsendme1 () where type OutType () = () gsendme1 _ _ _ () = return $ Right ()
单个MsgPkt实例
直接复用launch函数,约束输入为请求方向,输出为响应方向:
instance (MessagePack (MsgPkt m 'AnsMD), MessagePack (MsgPkt m 'AskMD)) => Gsendme1 (MsgPkt m 'AskMD) where type OutType (MsgPkt m 'AskMD) = MsgPkt m 'AnsMD gsendme1 = launch
元组递归实例
自动推导任意长度的元组实例,递归组合单个元素的结果:
instance (Gsendme1 a, Gsendme1 b) => Gsendme1 (a, b) where type OutType (a, b) = (OutType a, OutType b) gsendme1 timeout ip port (a, b) = do resA <- gsendme1 timeout ip port a resB <- gsendme1 timeout ip port b return $ (,) <$> resA <*> resB
4. 用法示例
传入MsgPkt请求元组,自动返回对应响应元组,完全保留类型安全:
-- 假设已定义defaultWarHead :: WarHead testLaunch :: IO (Either LaunchError (MsgPkt 'BombM 'AnsMD, MsgPkt 'RocketM 'AnsMD)) testLaunch = gsendme1 (Timeout 1000) (read "127.0.0.1") 8080 (BombMP defaultWarHead, RocketMP)
问题根源说明
之前的类型种类不匹配,是因为原Gsendme1类未明确关联MsgPkt的双类型参数(自定义Msg与MsgDir种类)。通过关联类型OutType,我们明确了输入(MsgPkt m 'AskMD)与输出(MsgPkt m 'AnsMD)的类型映射,同时利用递归元组实例自动适配任意长度的元组,无需手动编写每个元组的实例。
内容的提问来源于stack exchange,提问作者RandomB
相关产品推荐
相关产品推荐

