尝试写回退实例时重叠实例错误

时间:2014-12-12 23:40:49

标签: haskell typeclass functional-dependencies type-families overlapping-instances

我正在尝试执行与advanced overlap技巧类似的操作来定义具有重叠行为的实例。我正在尝试派生一个元组的实例,如果存在,将使用fst字段的实例,否则使用snd字段的实例(如果存在)。这最终导致关于重叠实例的看似错误的错误。

首先,我正在使用除OverlappingInstances以外的所有厨房水槽。

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

我还使用了多边形Proxy和类型级别,或:||:

import Data.Proxy

type family (:||:) (a :: Bool) (b :: Bool) :: Bool
type instance (:||:) False a = a
type instance (:||:) True a = True

A是一个非常简单的课程。 ThingAA个实例; ThingB没有。

class A x where
    traceA :: x -> String

data ThingA = ThingA
data ThingB = ThingB

instance A ThingA where
    traceA = const "ThingA"

下一部分的目标是为A编写一个(x, y)实例,只要有A xA y实例,就会对其进行定义。如果有A x个实例,则会返回("fst " ++) . traceA . fst。如果 A x个实例,但有B x个实例,则会返回("snd " ++) . traceA . fst

第一步是创建一个函数依赖项,通过匹配实例头来测试是否存在A实例。这是高级重叠文章的普通方法。

class APred (flag :: Bool) x | x -> flag

instance APred 'True ThingA
instance (flag ~ 'False) => APred flag x

如果我们可以确定xy是否都有A个实例,我们可以确定(x, y)是否会有一个实例。

instance (APred xflag x, APred yflag y, t ~ (xflag :||: yflag)) => APred t (x, y)

现在我将脱离高级重叠中的简单示例,并引入第二个函数依赖项来选择是否使用A xA y实例。 (对于BoolChooses,我们可以使用与SwitchA不同的类型,以避免与APred混淆。)

class Chooses (flag :: Bool) x | x -> flag

如果有A x个实例,我们将始终选择'True,否则选择'False

instance (APred 'True x) => Chooses 'True (x, y) 
instance (flag ~ 'False) => Chooses flag (x, y)

然后,与高级重叠示例一样,我定义了一个与A相同的类,除了选择的额外类型变量和每个成员的Proxy参数。

class SwitchA (flag :: Bool) x where
    switchA :: Proxy flag -> x -> String

这很容易为

定义实例
instance (A x) => SwitchA 'True (x, y) where
    switchA _ = ("fst " ++) . traceA . fst

instance (A y) => SwitchA 'False (x, y) where
    switchA _ = ("snd " ++) . traceA . snd

最后,如果SwitchA (x, y)的同一类型的Chooses实例,我们可以定义A (x, y)个实例。

instance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy flag)

到这里的一切都汇集得很漂亮。但是,如果我尝试添加

traceA (ThingA, ThingB)

我收到以下错误。

    Overlapping instances for Chooses 'True (ThingA, ThingB)
      arising from a use of `traceA'
    Matching instances:
      instance APred 'True x => Chooses 'True (x, y)
        -- Defined at defaultOverlap.hs:46:10
      instance flag ~ 'False => Chooses flag (x, y)
        -- Defined at defaultOverlap.hs:47:10
    In the first argument of `print', namely
      `(traceA (ThingA, ThingA))'

这里发生了什么?为Chooses 'True ...寻找实例时,为什么这些实例会重叠;如果instance flag ~ 'False => Chooses flag ...已知为flag,则'True实例不应该匹配吗?

相反,如果我尝试

traceA (ThingB, ThingA)

我收到错误

    No instance for (A ThingB) arising from a use of `traceA'
    In the first argument of `print', namely
      `(traceA (ThingB, ThingA))'

当我试图推动编译器执行它不设计的操作时,对任何事情的了解都会有所帮助。

编辑 - 简化

根据this answer的观察,我们可以完全摆脱Chooses并写下

instance (APred choice x, SwitchA choice (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy choice)

这解决了traceA (ThingB, ThingA)

的问题

1 个答案:

答案 0 :(得分:2)

要了解实际情况,请查看Chooses课程。具体来说,请注意它在False情况下并不是懒惰的(即,当它无法立即确定它应该具有值true时):

chooses :: Chooses b x =>  x -> Proxy b 
chooses _ = Proxy

>:t chooses (ThingA, ())
chooses (ThingA, ()) :: Proxy 'True
>:t chooses (ThingB, ())

<interactive>:1:1: Warning:
    Couldn't match type 'True with 'False
    In the expression: chooses (ThingB, ())

为什么它不懒惰的原因并不那么简单。最具体的实例,即

instance (APred 'True x) => Chooses 'True (x, y)
首先尝试

。要验证是否这样,编译器必须检查APred。此处instance APred 'True ThingA不匹配,因为您有ThingB。所以它落到了第二个实例,并将flag(在Chooses中)与False统一起来。那么约束APred 'True x就无法控制!因此,类型检查失败了。你得到的类型错误有点奇怪,但我认为这是因为你没有启用OverlappingInstances。当我用你的代码打开它时,我得到以下内容:

>traceA (ThingA, ThingA)
"fst ThingA"
>traceA (ThingB, ThingA)

<interactive>:43:1:
    Couldn't match type 'True with 'False
    In the expression: traceA (ThingB, ThingA)
    In an equation for `it': it = traceA (ThingB, ThingA)

这是预期的 - True和False类型无法统一。

修复非常简单。将类转换为类型函数。类型函数本质上是等价的,但“更懒惰”。这是非常手工波浪 - 抱歉,我没有更好的解释为什么它有效。

type family APred' x :: Bool where 
  APred' ThingA = True
  APred' x = False 

type family Chooses' x :: Bool where 
  Chooses' (x, y) = APred' x 

instance (Chooses' (x,y) ~ flag, SwitchA flag (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy flag)

现在你想“哦,不,我必须重写我的所有代码才能使用类型系列。”情况并非如此,因为您总是可以将类型系列“降低”为具有功能依赖性的类谓词:

instance Chooses' x ~ b => Chooses b x 

现在您的原始实例instance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where ...将按预期工作。

>traceA (ThingA, ThingA)
"fst ThingA"
>traceA (ThingB, ThingA)
"snd ThingA"