假设我有以下数据类型:
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
显然无法在这里发挥作用。
答案 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