简体   繁体   English

为什么需要减少上下文?

[英]Why is context reduction necessary?

I've just read this paper ("Type classes: an exploration of the design space" by Peyton Jones & Jones), which explains some challenges with the early typeclass system of Haskell, and how to improve it. 我刚读过这篇论文 (“类型类:Peyton Jones&Jones对设计空间的探索”),它解释了Haskell早期类型类系统的一些挑战,以及如何改进它。

Many of the issues that they raise are related to context reduction which is a way to reduce the set of constraints over instance and function declarations by following the "reverse entailment" relationship. 他们提出的许多问题都与上下文减少有关,这是通过遵循“反向蕴涵”关系来减少实例和函数声明的约束集的一种方法。

eg if you have somewhere instance (Ord a, Ord b) => Ord (a, b) ... then within contexts, Ord (a, b) gets reduced to {Ord a, Ord b} (reduction does not always shrink the number of constrains). 例如,如果你有某个instance (Ord a, Ord b) => Ord (a, b) ...那么在上下文中, Ord (a, b)会减少到{Ord a, Ord b} (减少并不总是缩小约束的数量)。

I did not understand from the paper why this reduction was necessary. 我不明白为什么这种减少是必要的。

Well, I gathered it was used to perform some form of type checking. 好吧,我收集它用于执行某种形式的类型检查。 When you have your reduced set of constraint, you can check that there exist some instance that can satisfy them, otherwise it's an error. 当你有一组简化的约束时,你可以检查是否存在一些可以满足它们的实例,否则就是一个错误。 I'm not too sure what the added value of that is, since you would notice the problem at the use site, but okay. 我不太确定它的附加价值是什么,因为你会注意到使用网站上的问题,但没关系。

But even if you have to do that check, why use the result of reduction inside inferred types? 但即使您必须进行检查,为什么要在推断类型中使用缩减结果? The paper points out it leads to unintuitive inferred types. 该文指出它导致了不直观的推断类型。

The paper is quite ancient (1997) but as far as I can tell, context reduction is still an ongoing concern. 这篇论文非常古老(1997),但就我所知,背景减少仍然是一个持续关注的问题。 The Haskell 2010 spec does mention the inference behaviour I explain above ( link ). Haskell 2010规范确实提到了我在上面解释的推理行为( 链接 )。

So, why do it this way? 那么,为什么这样呢?

I don't know if this is The Reason, necessarily, but it might be considered A Reason: in early Haskell, type signatures were only permitted to have "simple" constraints, namely, a type class name applied to a type variable. 我不知道这是否是The Reason,但是它可能被认为是一个原因:在早期的Haskell中,类型签名只允许具有“简单”约束,即应用于类型变量的类型类名。 Thus, for example, all of these were okay: 因此,例如,所有这些都没问题:

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

But none of these: 但这些都不是:

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

I think there was a feeling then -- and still today, as well -- that allowing the compiler to infer a type which one couldn't write manually would be a bit unfortunate. 我认为当时有一种感觉 - 现在仍然是 - 允许编译器推断出一个无法手动编写的类型会有点不幸。 Context reduction was a way of turning the above signatures either into ones that could be written by hand as well or an informative error. 上下文缩减是一种将上述签名转换为可以手写的签名信息错误的方法。 For example, since one might reasonably have 例如,因为一个人可能合理地拥有

instance Ord a => Ord (Tree a)

in scope, we could turn the illegal signature Ord (Tree a) => ... into the legal signature Ord a => ... . 在范围上,我们可以将非法签名Ord (Tree a) => ...转换为合法签名Ord a => ... On the other hand, if we don't have any instance of Eq for functions in scope, one would report an error about the type which was inferred to require Eq (a -> b) in its context. 另一方面,如果我们在范围内没有任何Eq实例,则会报告有关在其上下文中推断为需要Eq (a -> b)的类型的错误。

This has a couple of other benefits: 这还有其他一些好处:

  1. Intuitively pleasing. 直觉上令人愉悦。 Many of the context reduction rules do not change whether the type is legal, but do reflect things humans would do when writing the type. 许多上下文缩减规则不会改变类型是否合法,但确实反映了人类在编写类型时会做的事情。 I'm thinking here of the de-duplication and subsumption rules that let you turn, eg (Eq a, Eq a, Ord a) into just Ord a -- a transformation one definitely would want to do for readability. 我在这里考虑重复数据删除和包含规则,让你转向,例如(Eq a, Eq a, Ord a)Ord a - 一个肯定想要做的可转换性转换。
  2. This can frequently catch stupid errors; 这经常会发现愚蠢的错误; rather than inferring a type like Eq (Integer -> Integer) => Bool which can't be satisfied in a law-abiding way, one can report an error like Perhaps you did not apply a function to enough arguments? 而不是推断像Eq (Integer -> Integer) => Bool这样的类型,不能以一种守法的方式满足,可以报告错误, Perhaps you did not apply a function to enough arguments? . Much friendlier! 更友好!
  3. It becomes the compiler's job to pinpoint what went wrong. 编译器的工作就是找出出错的地方。 Instead of inferring a complicated context like Eq (Tree (Grizwump a, [Flagle (Gr ne) (Gr n' e') c])) and complaining that the context is not satisfiable, it instead is forced to reduce this to the constituent constraints; 而不是推断像Eq (Tree (Grizwump a, [Flagle (Gr ne) (Gr n' e') c]))这样复杂的上下文并且抱怨上下文不可满足,而是强制将其减少为成分限制; it will instead complain that we couldn't determine Eq (Grizwump a) from the existing context -- a much more precise and actionable error. 相反,它会抱怨我们无法从现有的上下文中确定Eq (Grizwump a) - 这是一个更加精确和可操作的错误。

I think this is indeed desirable in a dictionary passing implementation. 我认为这在传递实现的字典中确实是可取的。 In such an implementation, a "dictionary", that is, a tuple or record of functions is passed as implicit argument for every type class constraint in the type of the applied function. 在这样的实现中,“字典”,即元组或函数记录被作为所应用函数的类型中的每个类型类约束的隐式参数传递。

Now, the question is simply when and how those dictionaries are created. 现在,问题是这些词典的创建时间和方式。 Observe that for simple types like Int by necessity all dictionaries for whatever type class Int is an instance of will be a constant. 观察对于像Int这样的简单类型,所有类型类Int所有字典都是一个常量的实例。 Not so in the case of parameterized types like lists, Maybe or tuples. 在参数化类型(如列表, Maybe或元组)的情况下不是这样。 It is clear that to show a tuple, for instance, the Show instances of the actual tuple elements need to be known. 很明显,要显示元组,例如,需要知道实际元组元素的Show实例。 Hence such a polymorphic dictionary cannot be a constant. 因此,这样的多态字典不能是常数。

It appears that the principle guiding the dictionary passing is such that only dictionaries for types that appear as type variables in the type of the applied function are passed. 似乎指导字典传递的原则是这样的:只传递在所应用函数的类型中作为类型变量出现的类型的字典。 Or, to put it differently: no redundant information is replicated. 或者,换句话说:没有复制冗余信息。

Consider this function: 考虑这个功能:

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

The information that a tuple of show-able components is also showable, thus a constraint like Show (a,b) needs not to appear when we already know (Show a, Show b) . 可显示组件元组的信息也是可显示的,因此当我们已经知道(Show a, Show b)时,不需要出现像Show (a,b)这样的约束。

An alternative implementation would be possible, though, where the caller .would be responsible to create and pass dictionaries. 但是,另一种实现方式是可能的,其中调用者将负责创建和传递字典。 This could work without context reduction, such that the type of f would look like: 这可以在没有上下文减少的情况下工作,这样f的类型看起来像:

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

But this would mean that the code to create the tuple dictionary would have to be repeated on every call site. 但这意味着创建元组字典的代码必须在每个调用站点上重复。 And it is easy to come up with examples where the number of necessary constraints actually increases, like in: 并且很容易得出必要约束数量实际增加的示例,例如:

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)])

It is instructive to implement a type class/instance system with actual records that are explicitly passed. 使用显式传递的实际记录实现类型类/实例系统是有益的。 For example: 例如:

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

Once you do this you will probably easily recognize the need for "context reduction". 一旦这样做,您可能很容易认识到“减少上下文”的必要性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 `~`(代字号)在实例上下文中意味着什么,为什么在某些情况下需要解决重叠? - What does `~` (tilde) mean in an instance context, and why is it necessary to resolve overlap in some cases? 为什么这个eta扩展是必要的? - Why is this eta expansion necessary? Lambda演算Beta还原的具体步骤以及原因 - Lambda calculus beta reduction specific steps and why 尽管上下文认为有必要,但“无法根据上下文推论……” - “could not deduce … from the context …”, despite context holding what's necessary 为什么Haskell中的这种类型注释是必要的? - Why is this type annotation in Haskell necessary? 为什么即使在以下示例中,n约简也不适用于滤波器? - Why does n-reduction not apply to filter even in the following example? 为什么GHC使用图形缩减而不是超级组合器? - Why does GHC use graph reduction instead of supercombinators? 是否有必要在 class 声明的 class 上下文中指定每个超类? - Is it necessary to specify every superclass in a class context of a class declaration? 在llvm绑定中将函数作为形式参数传递时,上下文减少堆栈溢出 - Context reduction stack overflow when passing a function as a formal parameter in the llvm bindings 为什么没有必要在此函数中提供参数? - Why isn't it necessary to provide a parameter in this function?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM