我正在尝试执行与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
是一个非常简单的课程。 ThingA
有A
个实例; 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 x
或A 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
如果我们可以确定x
和y
是否都有A
个实例,我们可以确定(x, y)
是否会有一个实例。
instance (APred xflag x, APred yflag y, t ~ (xflag :||: yflag)) => APred t (x, y)
现在我将脱离高级重叠中的简单示例,并引入第二个函数依赖项来选择是否使用A x
或A y
实例。 (对于Bool
和Chooses
,我们可以使用与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)
答案 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"