You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GHC Haskell中实现Discord机器人库的类型安全插件?

类型安全的Discord机器人插件库问题

我最近在开发一个用于Discord机器人的Haskell库,核心目标是让用户能编写类型安全的插件。Discord发送的消息结构大致是这样的:

data EventType = FooEvent | BarEvent deriving (Eq, Show)
data Payload = Payload { eventType :: EventType , payload :: Maybe Value }

这里的eventType参数唯一决定了payload里JSON对象的类型,部分EventType可能没有payload。

期望实现的效果

我希望做到两点:

  • 用户可以在类型层面声明要处理的事件类型,类型系统会强制要求提供对应类型的处理函数;
  • 所有不同类型的插件能被当作同质实体统一处理,收到消息时,每个插件尝试把payload的JSON值转换为对应类型,转换成功就运行插件代码。

现有代码情况

示例Payload类型

我定义了对应事件的Payload类型,并实现了JSON序列化实例:

data FooPayload = FooPayload {foo :: String} deriving (Read, Eq, Show, Generic)
instance ToJSON FooPayload
instance FromJSON FooPayload

data BarPayload = BarPayload {bar :: String} deriving (Read, Eq, Show, Generic)
instance ToJSON BarPayload
instance FromJSON BarPayload

带关联类型的类型类

我创建了带关联类型的Convertible类型类,用来处理事件类型和Payload类型的转换:

class FromJSON (PayloadType ev) => Convertible (ev :: EventType) where
  type PayloadType ev :: *
  convert :: Value -> Maybe (PayloadType ev)
  convert = parseMaybe parseJSON

  -- 下面这个run函数的注释版本无法通过类型检查
  -- run :: Proxy (PayloadType ev) -> Plugin ev s -> Value -> IO ()
  run :: Plugin ev s -> Value -> IO ()
  run p v = undefined
  -- case convert v of
  --   Just v' -> runPlugin p v'  -- 因为类型族的非内射性,这里通不过类型检查
  --   Nothing -> return ()

对应的实例实现:

instance Convertible 'FooEvent where
  type PayloadType 'FooEvent = FooPayload

instance Convertible 'BarEvent where
  type PayloadType 'BarEvent = BarPayload

插件相关类型定义

我还定义了插件的类型,以及用于统一处理的包装类型:

data Plugin (ev :: EventType) s = Convertible ev => Plugin {
    initializePlugin :: IO s
  , runPlugin :: PayloadType ev -> IO ()
}

newtype RunnablePlugin = RunnablePlugin (Hide Plugin)
data Hide f = forall (ev :: EventType) s. Convertible ev => Hide (f ev s)

示例插件

我写了几个测试插件,并且可以把它们放到统一的列表里:

fooPlugin :: Plugin 'FooEvent ()
fooPlugin = Plugin {
    initializePlugin = return ()
  , runPlugin = \_ -> putStrLn "This is a foo plugin"
}

barPlugin :: Plugin 'BarEvent ()
barPlugin = Plugin {
    initializePlugin = return ()
  , runPlugin = \_ -> putStrLn "This is a bar plugin"
}

plugins :: [RunnablePlugin]
plugins = [
    RunnablePlugin $ Hide fooPlugin
  , RunnablePlugin $ Hide barPlugin
]

理想的运行逻辑

我希望能实现这样的runPlugins函数:

runPlugins :: Value -> [RunnablePlugin] -> IO ()
runPlugins val plugs = do
  forM_ plugs $ \(RunnablePlugin (Hide p)) -> do
    run p val
  return ()

比如调用runPlugins (toJSON (FooPayload "foo")) plugins时,应该输出This is a foo plugin。

当前遇到的问题

目前除了Convertible类型类里注释掉的run函数实现外,其他代码都能通过类型检查。我猜测问题出在统一处理插件的时候丢失了类型信息,没办法恢复,但我对这类类型编程不太熟悉。

想请教大家:这个需求能不能用标准的GHC Haskell实现?还是必须依赖依赖类型?


内容的提问来源于stack exchange,提问作者identity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:43:31