另一个类的实例的Show实例

时间:2014-09-21 01:36:24

标签: haskell

我有以下课程:

class SappState s where
    getTable   :: s -> SymbolTable
    getStack   :: s -> Stack Scope
    getScopeId :: s -> ScopeNum
    getAst     :: s -> Program
    putTable   :: SymbolTable -> s -> s
    putStack   :: Stack Scope -> s -> s
    putScopeId :: ScopeNum    -> s -> s
    putAst     :: Program     -> s -> s

我总是show data这个class实例,其中定义了函数。所以我使用以下代码概括

instance (SappState s) => Show s where
    show st = showT ++ showS ++ showI ++ showA
        where
            showT = getTable st ++ "\n"
            showS = "Scope Stack:\n"  ++ getStack st ++ "\n"
            showI = "Scope Number:\t" ++ getScopeId st ++ "\n"
            showA = getAst st ++ "\n"

但是GHC给了我以下错误:

SappMonad.hs:87:27:
    Illegal instance declaration for `Show s'
      (All instance types must be of the form (T a1 ... an)
       where a1 ... an are *distinct type variables*,
       and each type variable appears at most once in the instance head.
       Use -XFlexibleInstances if you want to disable this.)
    In the instance declaration for `Show s'

我应该使用FlexibleInstances pragma吗?我真的不明白它的作用,以及正确的方式,或者我是否应该放弃推广Show实例。


修改

我激活了pragma,它有这个新错误:

SappMonad.hs:88:10:
    Constraint is no smaller than the instance head
      in the constraint: SappState s
    (Use -XUndecidableInstances to permit this)
    In the instance declaration for `Show s'

我也激活了UndecidableInstances,现在一个简单的deriving Show将会中断:

data Architecture = Arch
    { archName :: String
    , types    :: Map.Map DataType Bytes
    } deriving (Show)

引发以下错误:

SappMonad.hs:39:17:
    Overlapping instances for Show (Map.Map DataType Bytes)
      arising from the 'deriving' clause of a data type declaration
    Matching instances:
      instance (Show k, Show a) => Show (Map.Map k a)
        -- Defined in `containers-0.5.0.0:Data.Map.Base'
      instance SappState s => Show s -- Defined at SappMonad.hs:89:10
    When deriving the instance for (Show Architecture)

第二次修改

我在该主题中搜索了更多内容并找到OverlappingInstances,添加了编译指示并编译,我认为它正常工作。但我觉得我对这些语言扩展太过分了。 我怎么能停止使用其中一些并仍然获得相同的功能?

1 个答案:

答案 0 :(得分:4)

不幸的是,对于这个问题,没有真正干净的解决方案。

您当前方法的最大缺点是,如果程序中的任何地方都有Show类似的“自动”实例,它们将完全停止工作,您将收到有关“重复实例”的错误。

基本问题是实例解析的工作原理是首先匹配实例声明的右侧。如果匹配,机器将承诺该声明,然后尝试解决左侧所需的任何事情。

因此,在您的情况下,为任何Show Foo搜索Foo的实例将无条件地与您的实例匹配,如果找不到SappState Foo实例,则解析将失败。

OverlappingInstances稍微减轻了这一点,因为它会首先查找更具体的实例。所以Show Int将使用普通实例解析,因为它特别提到了Int。但是如果范围内也有类似instance SappState2 a => Show a的内容,那么没有更具体实例的任何Foo都会导致重复的实例错误。

我建议让SappState的实施者亲自编写Show个实例。您可以通过提供实用程序函数showSappState :: SappState a => a -> String来降低成本,以便它们的实例可以只是

instance Show Foo where
    show = showSappState

[Show的正确实施还有一些方法,但适用相同的一般方法]

如果您想确保SappState的所有实例都是Show的实例,您可以使用超类来强制执行此操作:

class Show a => SappState a where
    ...

实际上有一些建议可以自动实现超类,这可以很好地解决你的问题,但是GHC还没有实现。一个例子是IntrinsicSuperclasses

为了完整起见,值得一提的是UndecidableInstances有点危险,因为它可能导致实例解析不能终止。保证它将终止的规则必然有点保守,因为问题是图灵完成,在某些情况下这些需要关闭。

instance SappState a => Show a的问题在于它将Show a约束减少为SappState a约束,并且由于正在搜索的新实例是相同的,因此没有明显的“进展”大小如旧。想象一下如果有人也写了instance Show a => SappState a会发生什么。