我有一个嵌套类型,我想使用关联的类型同义词来部分指定。下面是一些极其简化的代码,用于演示此问题:
{-# LANGUAGE TypeFamilies,
FlexibleInstances,
FlexibleContexts,
MultiParamTypeClasses #-}
f :: Int -> Int
f x = x+1
data X t i
newtype Z i = Z i deriving (Eq,Show)
newtype Y q = Y (T q)
class Qux m r where
g :: (T m) -> r
class (Integral (T a)) => Foo a where
type T a
-- ... functions
instance (Integral i) => Foo (X (Z i) i) where
type T (X (Z i) i) = i
instance (Foo q) => Num (Y q) -- where ...
instance (Foo q, Foo (X m' Int)) => Qux (X m' Int) (Y q) where
g x = fromIntegral $ f $ x
(即使使用UndecidableInstances)会导致编译错误:
Could not deduce (T (X m' Int) ~ Int)
我知道将此约束添加到Qux实例会使编译器满意。但是,我知道在我的程序中(T(X arg1 arg2))= arg2,所以我想弄清楚如何不必须写这个约束。
我的问题是:我可以让Haskell意识到当我将'Int'作为第二个参数写入X时,这是(同样)与同义词T(X a'Int)相同的东西吗?我意识到我正在使用关于我的实例看起来如何的“特殊”知识,但我认为应该有一种方法可以将它传达给编译器。
由于
答案 0 :(得分:1)
由于我不确定我是否理解这个问题,我将讨论您编写的代码;也许我漫无边际的某些部分要么指出你有用的方向,要么引发一些我可以回答的尖锐问题。那就是说:警告!在前面漫无目的地散步!
首先,我们来谈谈Bar
类。
-- class (i ~ S b) => Bar b i where
-- type S b
-- ...
由于我们知道约束i ~ S b
,我们不妨删除i
参数,我将在剩下的讨论中这样做。
class Bar b where type S b
-- or, if the class is empty, just
-- type family S b
-- with no class declaration at all
以下是这个新类的实例的外观:
instance Bar (Z i) where type S (Z i) = i
instance Bar (Z' i) where type S (Z' i) = i
如果对于任何类型的构造函数都是如此,您可以考虑将其写为一个实例:
-- instance Bar (f i) where type S (f i) = i
现在,我们来谈谈Foo
课程。要将其更改为与上述相符,我们将编写
class Bar (T a) => Foo a where type T a
您已声明了两个实例:
-- instance (Bar (Z i) i) => Foo (X (Z i) i) where
-- type T (X (Z i) i) = Z i
--
-- instance (Bar (Z' i) i) => Foo (X' (Z' i) i) where
-- type T (X (Z' i) i) = Z' i
我们可以像以前一样将第二个参数剥离到Bar
,但我们也可以做另一件事:因为我们知道有一个Bar (Z i)
实例(我们在上面声明了它!),我们可以带走实例约束。
instance Foo (X (Z i) i) where type T (X (Z i) i) = Z i
instance Foo (X (Z' i) i) where type T (X (Z' i) i) = Z' i
是否要将i
参数保持为X
取决于X
应该表示的内容。到目前为止,我们还没有更改任何类声明或数据类型的语义 - 只是它们是如何声明和实例化的。更改X
可能没有相同的属性;没有看到X
的定义就很难说。通过数据声明和足够多的扩展,上面的前奏编译。
现在,您的投诉:
您说以下内容无法编译:
class Qux a
instance Foo (X a' Int) => Qux (X a' Int)
instance Foo (X' a' Int) => Qux (X' a' Int)
但是,使用UndecidableInstances
,这会在这里编译。它需要UndecidableInstances
是有意义的:没有什么可以阻止某人稍后出现并声明像
instance Qux (X Y Int) => Foo (X Y Int)
Then, checking whether `Qux (X Y Int)` had an instance would require checking whether `Foo (X Y Int)` had an instance and vice versa. Loop.
你说,“它也想要实例约束S (T (X a'))) ~ Int
,尽管我知道在我的程序中,这些只是同义词。”我不知道第一个“它”是什么 - 我无法重现这个错误 - 所以我不能很好地回答这个问题。另外,正如我之前抱怨的那样,这种约束是不公平的:X :: (* -> *) -> * -> *
,因此X a' :: * -> *
和T
期望参数*
。所以我假设你的意思是S (T (X a' Int)) ~ Int
。
尽管有这些抱怨,但我们可以问为什么S (T (X a' Int)) ~ Int
无法从我们迄今为止的假设中证明。到目前为止,我们只为适合模式Foo
和X (Z i) i
的类型声明了X (Z' i) i
个实例。因此类型函数T
只能在其参数类型适合其中一种模式时减少。类型X a' Int
不太适合这些模式。我们可以把它塞进正确的模式:我们可以尝试使用X (Z Int) Int
来减少(比方说)。然后我们会找到T (X (Z Int) Int) ~ Z Int
,因此S (T (X (Z Int) Int) ~ S (Z Int) ~ Int
。
这回答了如何修复类型级别的减少,但没有解释如何修复任何未构建的代码。要做到这一点,我们必须找出为什么类型检查器需要从S (T (X a' Int))
到Int
的强制,并看看我们是否可以说服一个更具体(和可满足)的强制,如{{ 1}},或者,使用上面建议的通用S (T (X (Z Int) Int)) ~ Int
实例,Bar
。如果没有足够的代码来重现您的错误,我们当然无法帮助您。
你问,“我可以让Haskell意识到当我把'Int'作为第二个参数写入X时,这是(同样)与同义词S(T(X a'Int)相同的东西)?”。我根本不明白这个问题。你想以某种方式拥有可证明的平等S (T (X (f Int) Int)) ~ Int
?这是我从你的问题的字面解读中得到的解释。
在上下文中,我想你可能想问一下你是否可以获得可证明的平等X a Int ~ S (T (X a' Int))
;在那种情况下,答案是“绝对!”。我们会滥用上面b ~ S (T (X a b))
我们所知道的事实,将这个等式简化为更强的b ~ S (Z b)
。然后我们可以将上面的Z b ~ T (X a b)
实例替换为发出此声明的实例,而不是更多:
Foo
或者,我们可以假设另一个合理的等式instance Foo (X a b) where T (X a b) = Z b
;然后,为了证明T (X a b) ~ a
我们只需要证明S (T (X a b)) ~ b
(通过减少S a ~ b
),我们就可以编写另一个替代T
实例:
Foo