我正在尝试掌握GADTs,并且已经查看了GHC手册中的GADTs example。据我所知,可以用MultiParamTypeClasses做同样的事情:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
    FlexibleInstances, UndecidableInstances #-}

class IsTerm a b | a -> b where
  eval :: a -> b

data IntTerm  = Lit Int
              | Succ IntTerm
data BoolTerm = IsZero IntTerm
data If p a   = If p a a
data Pair a b = Pair a b

instance IsTerm IntTerm Int where
  eval (Lit i)      = i
  eval (Succ t)     = 1 + eval t

instance IsTerm BoolTerm Bool where
  eval (IsZero t)   = eval t == 0

instance (IsTerm p Bool, IsTerm a r) => IsTerm (If p a) r where
  eval (If b e1 e2) = if eval b then eval e1 else eval e2

instance (IsTerm a c, IsTerm b d) => IsTerm (Pair a b) (c, d) where
  eval (Pair e1 e2) = (eval e1, eval e2)

请注意,与GHC的eval示例中一样,我们的GADTs(在实例定义中扩展)具有完全相同的构造函数和完全相同的代码。

那么关于GADTs的所有毛病是什么?有什么我可以用GADTs做的事情,而不能是MultiParamTypeClasses吗?还是它们只是提供一种更简洁的方式来代替MultiParamTypeClasses来做事情?

最佳答案

您可以方便地将相同类型但构造函数不同的GADT值放入容器中,

map eval [Lit 1, If (IsZero (Lit 3)) (Lit 4) (Succ (Lit 6))]

这很简单,但是至少要使用具有功能依赖性的不同类型和MPTC来获得相同的内容是困难的。在您的Multiparameter类型类方法中,LitIf是不同类型的构造函数,因此需要包装类型将它们放入同一容器中。据我所知,包装器类型必须是存在类型àla
data Wrap t = forall a. (IsTerm a t) => Wrapper a

带着
instance IsTerm (Wrap t) t where
    eval (Wrapper e) = eval e

以确保某种类型的安全性以及在列表上使用map之类的eval函数的能力。这样一来,您回到GADT的途中就差了一半,而又没有那么方便。

我不确定GADT是否允许您做任何没有它们就无法实现的事情,但是有些事情会牺牲很大的优雅度。

关于haskell - GADT与MultiParamTypeClasses,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10994508/

10-09 02:53