首页 > 解决方案 > 上下文中的约束如何改变 Haskell 中的实例解析

问题描述

我试图了解向实例上下文添加约束如何更改 Haskell 中的实例解析。在这个例子中:

class C a where
  f :: a -> [Char]

instance {-# OVERLAPPABLE #-} C a where
  f = const "thing"

instance C Int where
  f = const "int"

instance {-# OVERLAPPING #-} C [a] where
  f [] = "empty list"
  f (x : xs) = "list of " ++ f x

main = do
  putStrLn $ f (1 :: Int)
  putStrLn $ f [(True :: Bool)]
  putStrLn $ f [(1 :: Int)]
  putStrLn $ f [[(1 :: Int)]]

输出是:

int
list of thing
list of thing
list of thing

最后两行不是我想要的。对于第 3 行,似乎编译器(或运行时?)在f为列表实例运行时不知道那aInt并且只使用默认C a实例。同样对于最后一行,它无法确定这a是另一个列表。但是,如果我向列表实例添加上下文:

instance {-# OVERLAPPING #-} (C a) => C [a] where
  f [] = "empty list"
  f (x : xs) = "list of " ++ f x

输出变为:

int
list of thing
list of int
list of list of int

...这就是我想要的。有人可以帮助解释该约束如何更改此示例中的实例分辨率吗?有什么一般规则我可以​​看看吗?

标签: haskelltypeclass

解决方案


本质上,实例选择仅在编译时执行。在运行时,周围没有类型信息,也没有存储在任何地方的实例列表来驱动实例选择。

那么,您的实例中发生了什么?考虑第一种情况:

instance {-# OVERLAPPING #-} C [a] where
  f [] = "empty list"
  f (x : xs) = "list of " ++ f x

假设f传递了一个类型列表[a0](为了清楚起见,我们使用一个新的变量名)。然后我们需要f x在最后一行输入 check 。上面的变量x是有类型a0的,所以GHC需要解决C a0。为此,GHC 必须选择一些实例。通常它会拒绝选择,instance C a因为它instance C Int也存在,而 GHC 不知道是否存在a0 = Int,因此只有手头的信息不能选择单个实例作为“最终”实例。

然而,“重叠”编译指示指示 GHC 在足以解决约束的通用实例中选择单个最佳实例。事实上,这是我们利用手头的信息所能做的最好的事情:唯一的其他合理选择是引发错误。

在这种情况下,要解决C a0我们需要在三个实例中选择一个,并且只有instance C a一般的足够匹配C a0(毕竟,a0可能是非Int,非列表类型)。所以我们选择那个。

相反,使用

instance {-# OVERLAPPING #-} (C a) => C [a] where
  f [] = "empty list"
  f (x : xs) = "list of " ++ f x

打开第四个选项来解决C a0,即使用C a0可用的上下文。当f被调用时,它被传递一个带有字典的隐式参数C a0(即f类型a0)。

所以,现在 GHC 有两个可行的选择:C a0使用C a0上下文求解(即使用隐式参数),或者求助于全局instance C a. 第一个更具体,因为它仅适用于a0而不适用于任何类型a,因此它被认为是“最好的”,并被选中。


推荐阅读