2014-09-29 55 views
3

我正在使用syntactic库来制作AST。为了评估AST为(哈斯克尔)值,我所有的节点都需要是语法类EvalEnv的一个实例:如何取消我的样板

class EvalEnv sym env where 
    compileSym :: proxy env -> sym sig -> DenotationM (Reader env) sig 

句法还提供了一个“默认”的实现:

compileSymDefault :: (Eval sym, Signature sig) 
    => proxy env -> sym sig -> DenotationM (Reader env) sig 

但在sig约束是在EvalEnv情况下无法访问,进行以下(比如,重叠)的实例是不可能的:

instance EvalEnv sym env where 
    compileSym = compileSymDefault 

所有我的用户自定义的AST节点GADTs,通常有多个构造,其中a参数总是满足了compileSymDefault约束:

data ADDITIVE a where 
    Add :: (Num a) => ADDITIVE (a :-> a :-> Full a) 
    Sub :: (Num a) => ADDITIVE (a :-> a :-> Full a) 

结果,我发现我的实例是所有EvalEnv外观像:

instance EvalEnv ADDITIVE env where 
    compileSym p Add = compileSymDefault p Add 
    compileSym p Sub = compileSymDefault p Sub 

这样板实例是所有AST节点相同,并且需要每个GADT构造的要被单独列出,作为GADT构造签名意味着compileSymDefault限制。

有什么办法可以避免必须列出每个节点类型的每个构造函数吗?

+0

无关,我不知道你应该将代理传递给'compileSym'。这不是必要的,并且使这个定义复杂化。 – Carl 2014-09-29 22:49:43

+0

@Carl如果不清楚,'compileSymDefault'由语法库提供。那里可能有一些原因。 – crockeea 2014-09-30 00:12:08

+0

“添加剂”类在哪里?我无法在任何“句法”模块中找到它,Hayoo也不能。 http://hayoo.fh-wedel.de/?query=syntactic+Additive – Cirdec 2014-09-30 03:16:05

回答

2

如果我正确地理解了这个问题,样板文件就产生于需要对每个构造函数使用模式匹配来将范围内的所需上下文。除了构造函数名称外,所有的案例分支都是相同的。

下面的代码使用了removeBoilerplate rank-2函数,该函数可用于将范围内的上下文。两个示例函数首先使用样板代码进行定义,然后转换为使用帮助程序removeBoilerplate函数。

如果您有许多GADT,您需要为每个GADT定制一个自定义的removeBoilerplate。因此,如果您需要为每种类型移除样板不止一次,则此方法非常有用。

我不熟悉语法是100%确定这会起作用,但它看起来有很好的机会。您可能需要稍微调整removeBoilerplate函数的类型。

{-# LANGUAGE GADTs , ExplicitForAll , ScopedTypeVariables , 
      FlexibleContexts , RankNTypes #-} 

class Class a where 

-- Random function requiring the class 
requiresClass1 :: Class a => a -> String 
requiresClass1 _ = "One!" 

-- Another one 
requiresClass2 :: Class a => a -> String 
requiresClass2 _ = "Two!" 

-- Our GADT, in which each constructor puts Class in scope 
data GADT a where 
    Cons1 :: Class (GADT a) => GADT a 
    Cons2 :: Class (GADT a) => GADT a 
    Cons3 :: Class (GADT a) => GADT a 

-- Boring boilerplate 
boilerplateExample1 :: GADT a -> String 
boilerplateExample1 [email protected] = requiresClass1 x 
boilerplateExample1 [email protected] = requiresClass1 x 
boilerplateExample1 [email protected] = requiresClass1 x 

-- More boilerplate 
boilerplateExample2 :: GADT a -> String 
boilerplateExample2 [email protected] = requiresClass2 x 
boilerplateExample2 [email protected] = requiresClass2 x 
boilerplateExample2 [email protected] = requiresClass2 x 

-- Scrapping Boilerplate: let's list the constructors only here, once for all 
removeBoilerplate :: GADT a -> (forall b. Class b => b -> c) -> c 
removeBoilerplate [email protected] f = f x 
removeBoilerplate [email protected] f = f x 
removeBoilerplate [email protected] f = f x 

-- No more boilerplate! 
niceBoilerplateExample1 :: GADT a -> String 
niceBoilerplateExample1 x = removeBoilerplate x requiresClass1 

niceBoilerplateExample2 :: GADT a -> String 
niceBoilerplateExample2 x = removeBoilerplate x requiresClass2 
2

你不能废弃你的样板,但你可以稍微减少它。 scrap your boilerplate和更新的GHC Generics代码都不能为您的GADT派生实例。可以用template haskell生成EvalEnv实例,但我不会讨论这个。

我们可以减少我们写的样板数量。我们遇到问题的想法是forall a对于任何ADDITIVE a都有Signature a实例。让我们制作这种事情的类。

class Signature1 f where 
    signatureDict :: f a -> Dict (Signature a) 

Dict是一个捕获约束的GADT。定义它需要{-# LANGUAGE ConstraintKinds #-}。或者,您可以在constraints包中从Data.Constraint导入。

data Dict c where 
    Dict :: c => Dict c 

要使用Dict构造函数捕获的约束,我们必须对它进行模式匹配。然后根据signatureDictcompileSymDefault编写compileSym。现在

compileSymSignature1 :: (Eval sym, Signature1 sym) => 
    proxy env -> sym sig -> DenotationM (Reader env) sig 
compileSymSignature1 p s = 
    case signatureDict s of 
     Dict -> compileSymDefault p s 

我们可以写出ADDITIVE及其实例,捕获的想法,总会有任何ADDITIVE a一个Signature a实例。

data ADDITIVE a where 
    Add :: (Num a) => ADDITIVE (a :-> a :-> Full a) 
    Sub :: (Num a) => ADDITIVE (a :-> a :-> Full a) 

instance Eval ADDITIVE where 
    evalSym Add = (+) 
    evalSym Sub = (-) 

instance Signature1 ADDITIVE where 
    signatureDict Add = Dict 
    signatureDict Sub = Dict 

instance EvalEnv ADDITIVE env where 
    compileSym = compileSymSignature1 

写了Signature1实例没有多大的好处超过写出EvalEnv实例。我们获得的唯一好处是我们捕获了一个可能在其他地方有用的想法,并且该实例的写法稍微简单一些。

+0

我一直在考虑将泛型作为一种可能性,但我会认为这是不可能的。 – crockeea 2014-09-30 16:33:00

+0

您只需要看到'GHC.Generics'不能为'ADDITIVE'派生'Generic1'实例就是添加'derived Generic1 ADDITIVE'实例。即使没有'{ - #LANGUAGE StandaloneDeriving# - }', '{ - #LANGUAGE DeriveGeneric# - }'和'import GHC.Generics',GHC也会给你提供错误信息。 – Cirdec 2014-09-30 16:40:41