从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
,类F
为IsTerm
。
此示例无法编译:给出与D
相同的拒绝。
答案 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
“预期类型”是方法的签名,并替换了实例头。要对类型不足够“一般”的方法主体进行类型检查,则需要:
- 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
的适用实例; Eval
改进为c
。第3步尤其令人期待:适用的实例可能在单独的模块中声明,该模块不在范围内;通常(与本示例不同),类型方案可能需要一个实例/可用的实例可能更具体或更通用/在类型更加完善之前无法选择。
但是我确实有一个悬而未决的问题:
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。