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.

我已经找到了两种如何编译这些代码的方法,其中没有一个我很满意。

  • 在使用flex函数的地方添加显式注释([1])。
  • 将flex所在的行移动到do块的末尾(例如注释掉[2])。
  • 我的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