正确标记AST

时间:2014-07-03 17:03:31

标签: haskell monads abstract-syntax-tree gadt name-binding

我一直在努力建立标记AST一段时间。我们来介绍一下这个问题:

data E a
  = V a
  | LitInt Int
  | LitBool Bool
  | FooIntBool (E a) (E a) -- er…
    deriving (Eq,Show)

对我来说,这段代码的问题在于FooIntBool。这个想法是它需要一个 int 表达式和一个 bool 表达式,并将它们粘合在一起。但E a可能是任何东西。鉴于上述E

的定义,这将是有效的
FooIntBool (LitInt 3) (LitInt 0)

您可以看到问题。然后,我们想要什么?标记的表达式。鉴于E的当前定义,这是不可能的,所以让我们介绍一些 GADTs

data E :: * -> * -> * where
  V          :: a -> E l a
  LitInt     :: Int -> E Int a
  LitBool    :: Bool -> E Bool a
  FooIntBool :: E Int a -> E Bool a -> E (Int,Bool) a

这太好了!我现在可以说出那种代码:

FooIntBool (V "x") (LitBool False)

问题在于我想让它成为monad或者应用程序。这是不可能的。考虑monad实现:

instance Monad (E l) where
  return = V

这显而易见且直截了当。让我们看看绑定实现:

  V x >>= f = f x -- obvious as well
  LitInt a >>= _ = LitInt a -- obvious yeah
  LitBool a >>= _ = LitBool a -- …
  FooIntBool a b >>= f = FooIntBool (a >>= ?) (b >>= ?) -- AH?

a >>= f以来,我们无法撰写b >>= ff :: a -> E l b。我还没有找到解决这个问题的方法,而且我真的很好奇如何处理它并且仍然可以使用de Bruijn索引(通过绑定)。

2 个答案:

答案 0 :(得分:4)

我认为您键入的AST不太可能以您想要的方式工作。变量是无类型的事实会受到伤害。试着想象用环境写一个翻译是什么感觉;您必须在环境中查找变量,而不是将结果强制转换为正确的类型,否则会失败并显示错误。因此,我将建议一个与类型变量略有不同的AST,以及一个尚未解释的类型参数重新排序。

data E v a where
  V          :: v a -> E v a
  LitInt     :: Int  -> E v Int
  LitBool    :: Bool -> E v Bool
  FooIntBool :: E v Int -> E v Bool -> E v (Int, Bool)

现在,据我所知,不可能为此定义一个守法的Monad实例。请注意,E的类型为(* -> *) -> * -> *;我们的目的可能更直观地将其视为(* -> *) -> (* -> *)。这与Monad期望的* -> *表面上是兼容的,至少如果您将E部分应用于某些v,那么这些类型会变得奇怪。我相信你已经意识到这一点,这就是为什么你把变量类型参数放在最后;这样做的预期效果是(>>=)代表替代。但是,如果我们使用我提出的这种新类型,那么它与Monad完全不兼容。

但是有一种不同的monad可以工作。我们可以将其类型从* -> *推广到(k -> *) -> (k -> *)(其中k只是*)。请再次注意,我使用括号来强调,就像大多数Monad的实例一样,E将被视为一元类型的构造函数。我们将使用自然转换而不是任何旧的Haskell函数:

type a ~> b = forall x. a x -> b x

(顺便说一下,(~>)的种类是(k -> *) -> (k -> *) -> *。)

要构建新的HMonad类型类,我们只需复制Monad并将(->)替换为(~>)即可。有一个复杂因素,就是我们必须翻转(>>=)的参数排序,以使类型成为现实:

class HMonad m where
  hreturn ::  a ~> m a
  hbind   :: (a ~> m b) -> (m a ~> m b)

我只会向您显示HMonad的{​​{1}}个实例,然后尝试解释它:

E

实际上,这看起来与instance HMonad E where hreturn = V hbind f e = case e of V v -> f v LitInt x -> LitInt x LitBool x -> LitBool x FooIntBool a b -> FooIntBool (hbind f a) (hbind f b) 实例对于无类型AST的版本完全相同。请注意,正如预期的那样,Monad只是注入一个变量,hreturn通过寻找变量并将函数应用于它们来执行一种类型安全的替换。这是因为排名类型较高。

您无法使用绑定包执行此操作,因为它使用hbind而不是此发烧友Monad。有可能(甚至已经多次完成)编写一个适用于类似AST的绑定版本,但不清楚它是否真的值得。

答案 1 :(得分:3)

如果你真的想要,可以写一个好的Monad实例。我没有检查它是否符合monad法律。

instance Monad (E l) where 
  return = V 

  V x >>= f = f x 
  LitInt a >>= _ = LitInt a 
  LitBool a >>= _ = LitBool a
  FooIntBool a b >>= f = FooIntBool (a >>= q.f) (b >>= r.f) where 

    q :: E (Int, Bool) t -> E Int t
    q (V x) = V x 
    q (FooIntBool x _) = x

    r :: E (Int, Bool) t -> E Bool t
    r (V x) = V x 
    r (FooIntBool _ x) = x