我一直在努力建立标记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 >>= f
和f :: a -> E l b
。我还没有找到解决这个问题的方法,而且我真的很好奇如何处理它并且仍然可以使用de Bruijn索引(通过绑定)。
答案 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