为什么需要减少上下文?

时间:2016-02-01 18:27:15

标签: haskell types type-inference typechecking

我刚读过this paper("类型类:对设计空间的探索" Peyton Jones& Jones),这解释了早期类型类系统的一些挑战。 Haskell,以及如何改进它。

他们提出的许多问题都与上下文缩减有关,这是一种通过遵循"反向蕴涵"来减少实例和函数声明的约束集的方法。关系。

e.g。如果你在某个地方instance (Ord a, Ord b) => Ord (a, b) ...然后在上下文中,Ord (a, b)会减少到{Ord a, Ord b}(减少并不会总是缩小约束数量。)

我不明白为什么这种减少是必要的。

好吧,我收集它用于执行某种形式的类型检查。当你有一组简化的约束时,你可以检查是否存在一些可以满足它们的实例,否则它就是一个错误。我不太确定它的附加值是什么,因为您会在使用网站上发现问题,但没关系。

但即使您必须进行检查,为什么要在推断类型中使用缩减结果?该论文指出它会导致不直观的推断类型。

这篇论文非常古老(1997),但就我所知,背景减少仍然是一个持续关注的问题。 Haskell 2010规范确实提到了我在上面解释的推理行为(link)。

那么,为什么这样呢?

2 个答案:

答案 0 :(得分:9)

我不知道这是否是The Reason,但是它可能被认为是一个原因:在早期的Haskell中,类型签名只允许有“简单”约束,即应用于类型的类型类名变量。因此,例如,所有这些都没问题:

Ord a => a -> a -> Bool
Eq a => a -> a -> Bool
Graph gr => gr n e -> [n]

但这些都没有:

Ord (Tree a) => Tree a -> Tree a -> Bool
Eq (a -> b) => (a -> b) -> (a -> b) -> Bool
Graph Gr => Gr n e -> [n]

我认为当时有一种感觉 - 现在仍然如此 - 允许编译器推断一个无法手动编写的类型会有点不幸。上下文缩减是一种将上述签名转换为可以手工编写的签名以及信息错误的方法。例如,因为一个人可能合理地拥有

instance Ord a => Ord (Tree a)

在范围内,我们可以将非法签名Ord (Tree a) => ...变为合法签名Ord a => ...。另一方面,如果我们在范围内没有Eq的任何实例,则会报告有关在其上下文中推断为需要Eq (a -> b)的类型的错误。

这还有其他一些好处:

  1. 直观的赏心悦目。许多上下文缩减规则不会改变类型是否合法,但确实反映了人类在编写类型时会做的事情。我在这里想到了重复数据删除和包含规则,让你转向,例如(Eq a, Eq a, Ord a)只是Ord a - 为了便于阅读,我们肯定会想做一个转型。
  2. 这可以经常发现愚蠢的错误;可以报​​告像Eq (Integer -> Integer) => Bool这样的错误,而不是推断像Perhaps you did not apply a function to enough arguments?这样的类型,而这种类型不能以守法的方式满足。非常友好!
  3. 找出错误是编译器的工作。而不是像Eq (Tree (Grizwump a, [Flagle (Gr n e) (Gr n' e') c]))那样推断复杂的上下文并抱怨上下文不可满足,而是强制将其减少到组成约束;相反,它会抱怨我们无法从现有的上下文中确定Eq (Grizwump a) - 这是一个更加精确和可操作的错误。

答案 1 :(得分:6)

我认为这在传递实现的字典中确实是可取的。在这样的实现中,"字典",即函数的元组或记录作为应用函数类型中每个类型类约束的隐式参数传递。

现在,问题在于何时以及如何创建这些词典。请注意,对于像Int这样的简单类型,所有类型类Int的字典都是一个常量的实例。 对于列表,Maybe或元组等参数化类型,情况并非如此。很明显,要显示元组,例如,需要知道实际元组元素的Show个实例。因此,这样的多态字典不能是常数。

似乎指导字典传递的原则是,只传递在所应用函数类型中作为类型变量出现的类型的字典。或者,换句话说:没有复制冗余信息。

考虑这个功能:

 f :: (Show a, Show b) => (a,b) -> Int
 f ab = length (show ab)

可显示组件元组的信息也是可显示的,因此当我们已经知道Show (a,b)时,不需要显示像(Show a, Show b)这样的约束。

但是,调用者可能负责创建和传递字典,但也可以使用替代实现。这可以在没有上下文减少的情况下工作,这样f的类型看起来像:

f :: Show (a,b) => (a,b) -> Int

但这意味着必须在每个呼叫站点上重复创建元组字典的代码。并且很容易得出必要约束实际增加的例子,如:

g :: (Show (a,a), Show(b,b), Show (a,b), Show (b, a)) => a -> b -> Int
g a b = maximum (map length [show (a,a), show (a,b), show (b,a), show(b,b)])

使用显式传递的实际记录实现类型类/实例系统是有益的。例如:

data Show' a = Show' { show' :: a -> String }
showInt :: Show' Int
showInt = Show' { show' = intshow } where
      intshow :: Int -> String
      intshow = show

一旦你这样做,你可能很容易认识到"背景减少的需要"。