成对反向对的棘手类型签名

时间:2015-07-17 14:34:54

标签: haskell

假设我有以下数据类型:

data D a b = D (a,b) (b,a)

我想在其上定义以下功能:

apply f (D x y) = D (f x) (f y)

apply的类型签名是什么?

以下是f的一些例子:

f :: a -> a -- OK
f :: (a, b) -> (b, a) -- OK
f :: (a, b) -> ((a, c), (b, c)) -- OK

在上述所有情况中,我们最终得到一个有效的类型D.

但这不合适:

f :: (a, b) -> (a, a)

因为当我们通过apply发送此类功能时,我们最终不得不尝试构建D (a,a) (b,b),除非a = b,否则该x = apply _ (D (1::Int,'a') ('b',2::Int)) 无效。

我似乎无法找出一个类型签名来表达这一切?另外,总的来说,有没有办法让GHC告诉我这些签名应该是什么?

键入的洞编辑:

为了尝试使用类型孔找到类型,我尝试了以下方法:

Found hole ‘_’ with type: (Int, Int) -> (b, b)
Where: ‘b’ is a rigid type variable bound by
           the inferred type of x :: D b b

得到了:

f :: (Int, Int) -> (b, b)

在我看来这是胡说八道Cache显然无法在这里发挥作用。

2 个答案:

答案 0 :(得分:8)

多种类型适合apply,但推断的((t, t) -> (b, b)) -> D t t -> D b b是最明智和最有用的类型。替代方案将更高级别,所以让我们启用语言扩展:

{-# LANGUAGE RankNTypes #-}

首先,我们可以让apply id工作:

apply :: (forall a. a -> a) -> D a b -> D a b
apply f (D x y) = D (f x) (f y)

但是,现在id唯一的函数,apply可以使用该函数(类型forall a. a -> a的所有函数都等于id) 。

这是另一种类型:

apply :: (forall a. a -> (c, c)) -> D a b -> D c c
apply f (D x y) = D (f x) (f y)

但这也是有代价的。现在f - s只能是忽略D之前字段的常量函数。所以apply (const (0, 0))有效,但我们无法检查f的论点。

相比之下,推断类型非常有用。我们可以用它来表达转换,查看D中包含的实际数据。

此时,我们可能会问:为什么GHC会推断它的推断?毕竟,某些函数可以使用其他类型,但不能使用默认类型。有时推断排名较高的类型会更好吗?嗯,这些类型通常非常有用,但推断它们是不可行的。

排名2类型的类型推断相当复杂,也不太实用,因为无法推断最常见的类型。使用rank-1推断,我们可以推断出比同一表达式的所有其他有效类型更通用的类型。排名2类型没有这样的保证。并且对3级及以上类型的推断仅为undecidable

由于这些原因,GHC坚持排名第一推理,因此它永远不会在函数参数内推断forall - s类型。

答案 1 :(得分:0)

将事情置于极端的普遍性,我们想要一种类似于

的类型
apply :: tf -> D a b -> D c d

其中tf代表f的类型。要将f应用于(a,b)并获取(c,d),我们需要

tf ~ (a,b) -> (c,d)

要将f应用于(b,a)以获取(d,c),我们需要

tf ~ (b,a) -> (d,c)

如果我们启用TypeFamilies(或GADTs),我们就会获得表达的神奇约束:

{-# LANGUAGE TypeFamilies #-}

apply :: (((a,b) -> (c,d)) ~ tf,
          ((b,a) -> (d,c)) ~ tf) =>
         tf -> D a b -> D c d

我怀疑这是最常用的类型之一。不幸的是,它非常狂野;通过特定应用程序是否通过类型检查程序并不总是那么容易。另请注意,由于我个人不理解的原因,您无法使用

来可靠地定义专业化
apply' :: ...
apply' = apply

相反,您有时必须使用

apply' f = apply f