GADT 是否会破坏 Haskell 中的等式推理

Do GADTs break equational reasoning in Haskell

我有以下使用操作包中的 ProgramT 的解释器

{-# LANGUAGE GADTs #-}
import Control.Monad ((<=<))
import Control.Monad.Operational

data Motor' a where
  --Define GADT

serialNewportT :: (Monad m, MonadIO m) => ProgramT Motor' m a -> m a
serialNewportT = eval <=< viewT
  where
    eval :: (Monad m, MonadIO m) => ProgramViewT Motor' m v -> m v
    eval (Return x) = return x
    eval (x :>>= k) = f x >>= serialNewportT  . k

f :: (Monad m, MonadIO m) => Motor' a -> m a
f = undefined -- excluded for brevity

就目前而言,该代码目前可以进行类型检查。但是,我希望能够切换解释器处理不同电机的方式,所以我想将 f 作为参数,而不是将其硬编码。我尝试使用以下代码进行此切换

{-# LANGUAGE GADTs #-}
import Control.Monad ((<=<))
import Control.Monad.Operational

data Motor' a where
  --Define GADT

serialNewportT :: (Monad m, MonadIO m) => (Motor' a -> m a) -> ProgramT Motor' m a -> m a
serialNewportT f = eval <=< viewT
  where
    eval :: (Monad m, MonadIO m) => ProgramViewT Motor' m v -> m v
    eval (Return x) = return x
    eval (x :>>= k) = f x >>= serialNewportT f . k

但是,此代码失败并显示错误消息

Couldn't match type ‘b’ with ‘c’
   ‘b’ is a rigid type variable bound by
       a pattern with constructor
         :>>= :: forall (instr :: * -> *) (m :: * -> *) a b.
                 instr b -> (b -> ProgramT instr m a) -> ProgramViewT instr m a,
       in an equation for ‘eval’
   ‘c’ is a rigid type variable bound by
       the type signature for
         serialNewportT :: (Monad m, MonadIO m) =>
                           (Motor' c -> m c) -> ProgramT Motor' m a -> m a
 Expected type: Motor' c
   Actual type: Motor' b

因为我只是将全局名称替换为相同类型的本地名称,所以我认为它应该可以顺利运行。如何让类型与 f 统一作为参数?

你没有正确翻译 f 的类型。在原代码中,f的类型是forall a . (..) => Motor' a -> m a。它可以用类型 Motor' a 实例化为 any a,但在非工作代码中你声明它的类型完全相同 aProgramT Motor' m a 中一样,而在函数体中,您在 Motor' b 上调用 f 用于其他一些(存在量化的)类型 b

你只需要更高级别的类型:

serialNewportT :: (Monad m, MonadIO m) => (forall x . Motor' x -> m x) -> ProgramT Motor' m a -> m a