使用类约束统一关联的类型同义词

时间:2012-07-15 16:27:13

标签: haskell type-families

我有一个嵌套类型,我想使用关联的类型同义词来部分指定。下面是一些极其简化的代码,用于演示此问题:

{-# 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)相同的东西吗?我意识到我正在使用关于我的实例看起来如何的“特殊”知识,但我认为应该有一种方法可以将它传达给编译器。

由于

1 个答案:

答案 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无法从我们迄今为止的假设中证明。到目前为止,我们只为适合模式FooX (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