GHC不会选择唯一可用的实例
我正在尝试在Haskell中编写CSS DSL,并尽可能使语法尽可能靠近CSS。 一个难点是某些术语既可以作为属性又可以作为价值出现。 例如flex:你可以在CSS中使用“display:flex”和“flex:1”。
我让自己受到Lucid API的启发,它覆盖了基于函数参数的函数来生成属性或DOM节点(有时也会共享名称,例如<style>
和<div style="...">
)。
无论如何,我遇到了一个问题,即GHC未能检查代码(Ambiguous类型变量),它应该选择两个可用的类型实例之一。 只有一个实例适合(事实上,在类型错误GHC打印“这些潜在的实例存在:”,然后它只列出一个)。 我很困惑,考虑到单个实例的选择,GHC拒绝使用它。 当然,如果我添加明确的类型注释,那么代码将被编译。 下面的完整例子(只有依赖是mtl,对于Writer)。
{-# LANGUAGE FlexibleInstances #-}
module Style where
import Control.Monad.Writer.Lazy
type StyleM = Writer [(String, String)]
newtype Style = Style { runStyle :: StyleM () }
class Term a where
term :: String -> a
instance Term String where
term = id
instance Term (String -> StyleM ()) where
term property value = tell [(property, value)]
display :: String -> StyleM ()
display = term "display"
flex :: Term a => a
flex = term "flex"
someStyle :: Style
someStyle = Style $ do
flex "1" -- [1] :: StyleM ()
display flex -- [2]
和错误:
Style.hs:29:5: error:
• Ambiguous type variable ‘a0’ arising from a use of ‘flex’
prevents the constraint ‘(Term
([Char]
-> WriterT
[(String, String)]
Data.Functor.Identity.Identity
a0))’ from being solved.
(maybe you haven't applied a function to enough arguments?)
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instance exist:
one instance involving out-of-scope types
instance Term (String -> StyleM ()) -- Defined at Style.hs:17:10
• In a stmt of a 'do' block: flex "1"
In the second argument of ‘($)’, namely
‘do { flex "1";
display flex }’
In the expression:
Style
$ do { flex "1";
display flex }
Failed, modules loaded: none.
我已经找到了两种如何编译这些代码的方法,其中没有一个我很满意。
我的API和Lucid之间的一个区别是Lucid术语总是有一个参数,而Lucid使用fundeps,这大概给GHC类型检查器提供了更多信息来处理(选择正确的类型类实例)。 但在我的情况下,这些术语并不总是有一个参数(当它们作为值出现时)。
问题是仅当StyleM
使用()
参数化时, String -> StyleM ()
的Term
实例才存在。 但在一个像块一样的块
someStyle :: Style
someStyle = Style $ do
flex "1"
return ()
没有足够的信息知道哪些是flex "1"
的类型参数,因为返回值被丢弃。
这个问题的一个常见解决方案是“限制技巧”。 它需要键入平等的限制,所以你必须启用{-# LANGUAGE TypeFamilies #-}
或{-# LANGUAGE GADTs #-}
和调整这样的实例:
{-# LANGUAGE TypeFamilies #-}
instance (a ~ ()) => Term (String -> StyleM a) where
term property value = tell [(property, value)]
这告诉编译器:“你不需要知道获取实例的确切类型a
,所有类型都有一个!但是,一旦确定了实例,就会发现类型为()
!”
这个诀窍是亨利福特的类型类版本“只要它是黑色的,你可以拥有任何你喜欢的颜色。” 尽管模糊不清,编译器仍然可以找到一个实例,并且通过查找实例给他提供了足够的信息来解决模糊问题。
它的工作原理,因为Haskell的实例解析从不回溯,所以一旦一个实例“匹配”,编译器必须遵守它在实例声明的前提发现任何平等,或抛出一个类型的错误。
只有一个实例适合(事实上,在类型错误GHC打印“这些潜在的实例存在:”,然后它只列出一个)。 我很困惑,考虑到单个实例的选择,GHC拒绝使用它。
类型类是开放的; 任何模块都可以定义新的实例。 因此,当检查类型类的使用时,GHC从不假定它知道所有实例。 (像OverlappingInstances
这样的不好的扩展可能是个例外)。那么逻辑上,对于一个问题“是否有CT
实例”的唯一可能答案是“是”和“我不知道”。 要回答“否”风险与确定实例CT
另一部分程序不一致。
所以,你不应该想象编译器迭代每个已声明的实例,并且看看它是否适合于感兴趣的特定使用站点,因为它会对所有“我不知道”做什么? 相反,该过程像这样工作:推断可以在特定使用站点使用的最常用类型,并查询实例存储以获取所需实例。 查询可以返回比所需要的更普通的实例,但它永远不会返回更具体的实例,因为它必须选择要返回哪个更具体的实例; 那么你的程序是不明确的。
考虑这种差异的一种方法是,遍历C
所有声明实例将在实例数量上占用线性时间,而查询特定实例的实例存储只需检查不变数量的潜在实例。 例如,如果我想打字检查
Left True == Left False
我需要一个Eq (Either Bool t)
实例Eq (Either Bool t)
,它只能由一个实例来满足
instance Eq (Either Bool t)
instance Eq (Either a t) -- *
instance Eq (f Bool t)
instance Eq (f a t)
instance Eq (g t)
instance Eq b
(标记为*
的实例是实际存在的实例,并且在标准Haskell(不含FlexibleInstances
)中,它是这些实例中唯一合法声明的实例;对形式C (T var1 ... varN)
实例的传统限制使这一步变得简单,因为总会有一个潜在的实例。)
如果实例存储在类似哈希表的类中,那么无论Eq
的已声明实例的数量(这可能是一个相当大的数量),此查询都可以在固定时间内完成。
在这一步中,只检查实例头( =>
右侧的东西)。 除了“是”答案之外,实例存储可以返回对实例上下文来的类型变量( =>
左侧的内容)的新约束。 这些限制因此需要以相同的方式解决。 (这就是为什么实例被认为是重叠的,如果它们有重叠的头部,即使它们的上下文看起来是相互排斥的,为什么instance Foo a => Bar a
几乎不是一个好主意。)
在你的情况,因为任何类型的值可以被丢弃do
记号,我们需要一个实例Term (String -> StyleM a)
。 实例Term (String -> StyleM ())
更具体,所以在这种情况下无用。 你可以写
do
() <- flex "1"
...
使需要的实例更具体化,或者通过使用类型相等技巧使提供的实例更一般化,如danidiaz的回答中所解释的。
链接地址: http://www.djcxy.com/p/43265.html上一篇: GHC doesn't pick the only available instance
下一篇: Monomorphism restriction triggered when generic instance defined