根据标准 Haskell 重写 GADT 代码?

Rewriting GADT code in terms of standard Haskell?

我一直在努力理解文章 The Operational Monad Tutorial by Heinrich Apfelmus, where he uses GADTs。作为脑力练习,我尝试重写他的代码示例,使它们不再使用 GADT。我做不到,现在我确定这是我的能力有限,还是根本问题。

r/haskell 上,Edward Kmett 指出“如果您有等级 N 类型,您始终可以通过 class 将 GADT 转换为最终 [原文如此] 无标签表示。"

但是如果我也不想使用 Rank N 类型怎么办?我想,我不得不牺牲一些东西,但我应该仍然能够做到。

所以,如果我愿意牺牲一些类型安全性,使用虚类型、typeclasses 等等,那么我可以编写一个 eval 函数用于表达式(有点)像下面的表达式,没有使用 LANGUAGE 扩展?

                           -- using GADTs :
data Expr = I Int  |       -- Int  -> Expr Int
            B Bool |       -- Bool -> Expr Bool
            Add Expr Expr  -- Expr Int -> Expr Int -> Expr Int
            Eq  Expr Expr  -- Expr 1   -> Expr a   -> Expr Bool

如果没有 GADT,我们需要一个 Value 求和类型,包含所有可能的结果。我们还需要使我们的评估函数部分或 return 类似于 Maybe Value.

data Value = VI Int | VB Bool

eval :: Expr -> Value
eval (I i) = VI i
eval (B b) = VB b
eval (Add e1 e2) = case (eval e1, eval e2) of
   (VI i1, VI i2) -> VI (i1+i2)
   _              -> error "runtime type error"
...

没有 GADT,你不知道 Expr 是否类型正确,所以你只能实现一个 eval,这可能会引发错误。

eval :: Expr -> Maybe (Either Int Bool)
eval (I n) = pure (Left n)
eval (B b) = pure (Right b)
eval (Add e1 e2) = do
  x1 <- eval e1
  x2 <- eval e2
  case (x1, x2) of
    (Left n1, Left n2) -> pure (Left (n1 + n2))
    _                  -> Nothing
eval (Eq e1 e2) = do
  x1 <- eval e1
  x2 <- eval e2
  case (x1, x2) of
    (Left  n1, Left n2)  -> pure (Right (n1 == n2))
    (Right b1, Right b2) -> pure (Right (b1 == b2))
    _                    -> Nothing