刚性类型的可变故障/可疑性

时间:2019-03-07 05:38:56

标签: haskell types type-inference gadt

this q about GADTs开始,我正在尝试构建一个EDSL(对于本文中的示例),但是没有GADT。我已经做了一些工作,可以避免将AST的数据类型加倍。但相反,它似乎使代码加倍。所以我尝试精简代码,这是我遇到麻烦的地方...

而不是像这样的GADT

data Term a where
  Lit ::      Int -> Term Int
  Inc :: Term Int -> Term Int
  IsZ :: Term Int -> Term Bool
  -- etc

将每个构造函数声明为单独的数据类型

data Lit   = Lit Int  deriving (Show, Read)
data Inc a = Inc a    deriving (Show, Read)
data IsZ a = IsZ a    deriving (Show, Read)
-- etc

然后EDSL用户可以输入和show个条款

aTerm = IsZ (Inc (Lit 5))

illtypedTerm = Inc (IsZ (Lit 5))    -- accepted OK and can show
                                    -- but can't eval [good!]

然后发现它们都是Term,需要正确输入

data Term a = ToTerm { fromTerm :: a} deriving (Show, Eq)

class IsTerm a c  | a -> c 
instance IsTerm Lit (Term Int) 
-- etc

FunDep会从原始GADT中捕获返回类型。然后eval可以使用该Term类型

class Eval a   
  where eval :: (IsTerm a c) => a -> c 
                             -- hmm this makes 'c' a rigid tyvar
instance Eval Lit  where    
         eval (Lit i) = -- undefined     -- accepted OK, infers :: Term Int
                        ToTerm i         -- rejected

方程eval (Lit i) = undefined(注释掉)编译正常,GHC推断eval (Lit 5) :: Term Int。但是如果我放= ToTerm i

* Couldn't match expected type `c' with actual type `Term Int'
  `c' is a rigid type variable bound by
    the type signature for:
      eval :: forall c. IsTerm Lit c => Lit -> c
* Relevant bindings include
    eval :: Lit -> c

如果GHC可以(通过FunDep)推断c的{​​{1}}必须是Term Int,为什么不能为= undefined呢?推断为= ToTerm i的专用类型信号是强制性的吗?但是eval :: forall c. IsTerm Lit c => Lit -> c是返回类型,因此不是RankN(?)

如何避免此错误?我有工作

  • c仅复制class (IsTerm a c) => Eval a c | a -> c where ...中的所有实例头,因此超类约束仅充当皮带和括号。 (那是我想要避免的两倍。)

  • IsTerm。但是,type family ToTerm a ...; class Eval a where eval :: a -> ToTerm a的实例又将Eval的所有实例加倍,而且还需要在ToTerm调用之间具有很多~约束的大型上下文。

我可以扔掉类ToTerm,然后将所有术语推论放在类IsTerm上。但我试图与GADT风格保持一致,以便让许多应用程序“客户端”共享相同的Term定义。

添加: [3月14日]

2011年的论文System F with Type Equality Coercions的第2.3节中有此示例(在讨论功能依赖项时)

Eval
  

使用FC,这个问题[在实例class F a b | a -> b instance F Int Bool class D a where { op :: F a b => a -> b } instance D Int where { op _ = True } 中键入op的定义]可以很容易地解决:D Int的字典中的强制启用了{{1 }}强制转换为F类型。

  • 该示例似乎与q相同,类为op,FunDep为b,类FIsTerm

  • 此示例无法编译:给出与D相同的拒绝。

3 个答案:

答案 0 :(得分:1)

  

如果GHC可以(通过FunDep推断)c必须是Term Int,表示=未定义

不能。如果尝试使用library("docxtractr") sourcesSummary <- lapply(files, function(x){ doc <- read_docx(x) kingsTbls <- docx_extract_all_tbls(doc) sources <- docx_extract_tbl(doc, 6, header = FALSE) sources <- data.frame(sources) # The below two lines are the issue # sources[9,3:4] <- sources[9,2:3] sources[24,3:4] <- sources[24,2:3] sources #<- New code! }) ,则会收到相同的刚性类型变量错误。如果您使用类型化的孔undefined :: Term Int,则会看到它正在推断= _undefined。我不知道为什么,但是功能依赖项似乎仅在 undefined :: c应用于eval时使用,而不是在定义它时使用。

那呢?

Lit

答案 1 :(得分:0)

请注意,IsTerm约束位于方法eval上,而不是Eval的超类约束-之所以不能存在,因为{的返回类型c {1}}不是该类的参数。

然后孤立地考虑eval会产生误导。考虑instance Eval Lit的实例,以及Inc的实例:

IsTerm

instance ( {-# tba #-} ) => Eval (Inc a) where eval (Inc i) = ToTerm $ 1 + fromTerm (eval i) instance (IsTerm a (Term int)) => IsTerm (Inc a) (Term Int) 约束应包括什么?

  • 方法主体具有对tba的递归调用,因此它需要eval
  • 方法主体的Eval a需要1 + ... eval ...才能返回eval,但是Int约束不提供该约束(因为它没有提及返回类型) 。
  • 约束Eval是否可以提供约束?否:IsTerm a (Term Int)暗示({充其量)IsTerm,而不是相反。

因此,无需过多重复就可以挂在一起的唯一方法是:

  • 编译器看到对Eval的递归调用;神奇地为其添加了eval约束;为IsTerm a c1找到了IsTerm的适用实例;然后计算出a实例产生了IsTerm
  • 但是,如果它仅有光秃的c1 ~ Term Int,就无法知道哪个实例。因此,将约束保留为在某项a上将eval应用于(Inc x)的通缉令吗?

对比一下,在x的GADT定义中,其参数必须为Inc。这是束腰带,可防止EDSL用户输入错误的字词:

Term Int

关于 Inc :: Term Int -> Term Int 的特定拒绝消息,我认为Hugs的措辞更清楚:

instance Eval Lit

“预期类型”是方法的签名,并替换了实例头。要对类型不足够“一般”的方法主体进行类型检查,则需要:

  1. 将方法的约束作为检查方法主体的一部分;
  2. 看到该约束的类具有FunDep,并且从参数类型改进了返回类型;
  3. - Inferred type is not general enough *** Expression : eval *** Expected type : (Eval Lit, IsTerm Lit a) => Lit -> a *** Inferred type : (Eval Lit, IsTerm Lit (Term Int)) => Lit -> Term Int 的实例(以及递归地查找该实例的任何约束的实例)找到IsTerm的适用实例;
  4. 通过FunDep将Eval改进为c

第3步尤其令人期待:适用的实例可能在单独的模块中声明,该模块不在范围内;通常(与本示例不同),类型方案可能需要一个实例/可用的实例可能更具体或更通用/在类型更加完善之前无法选择。

但是我确实有一个悬而未决的问题:

  • 如果方法的主体类型比通过替换实例头推断出的类型更具体,那出什么问题了?
  • Hugs消息中的推断类型更具体,但还带有约束,因此必须将其释放才能接受程序。
  • 如果Term int的调用者希望使用更通用的类型,则可以替换更具体的结果。

答案 2 :(得分:0)

我要发布另一个答案,因为这是一个令人惊讶/罕见的野兽:在Hugs下编译并工作的代码;但不会根据GHC(8.6.4)进行编译。那是除非这里的某人可以看到我出了错并且可以修复它。

首先考虑2011年论文中的 Addit:示例。这只能在Hugs中实现(WinHugs“最新”版本于2006年9月发布)

class F a b | a -> b
instance F Int Bool

class D a where { op :: (F a b) => a -> b } 
instance (TypeCast Bool b') => D Int where { op _ = typeCast True }

除了instance D Int之外,该代码与论文中的代码相同。我添加了一个TypeCast约束(并调用了它的方法),这看起来很奇怪,因为类型变量b'并未在任何地方使用。

我已经尝试了各种方法来使其在GHC中进行编译:

  • TypeCast大约等于GHC的~约束,这不是Hugs的功能;但是将其更改为~不会使GHC接受代码。
  • 我尝试了各种类型的注释,包括ScopedTypeVariables。娜达。

有关更多详细信息,请参见Trac #16430,包括TypeCast的定义-这是完全标准的。

然后原始q的代码如下(仅拥抱)

class    Eval a   where
         eval :: (IsTerm a c) => a -> c 

instance (TypeCast (Term Int) c') => Eval Lit  where
         eval (Lit i) = typeCast (ToTerm i)       -- rejected without typeCast

instance (Eval a, IsTerm a (Term Int), TypeCast (Term Int) c')
         => Eval (Inc a)  where
         eval (Inc i) = typeCast (ToTerm $ 1 + fromTerm (eval i))
instance (Eval a, IsTerm a (Term Int), TypeCast (Term Bool) c') 
         => Eval (IsZ a)  where
         eval (IsZ i) = typeCast (ToTerm $ 0 == fromTerm (eval i))

eval aTerm(请参见上面的q)现在返回(ToTerm False)eval illtypedTerm会按预期给出拒绝消息。

所有这些实例都以TypeCast为悬挂类型变量的特征(如果“功能”是正确的词)。对于是否将Eval设为具有两个仅从IsTerm开始重复的参数的类,我是否实现了重复代码的保存尚有待商de。