考虑函数f'的以下声明。使用带有Frames库(定义UnColumn和AllAre)的单例,以及使用withSing的包装函数。
*******video******** - file1\n
./video.sh
*******video******** - file1\n
*******video******** - file2\n
./video.sh
*******video******** - file2\n
*******video******** - file3\n
./video.sh
这似乎工作正常。但是当我添加一个类型注释时,类型检查失败并显示错误无法推断:{-# LANGUAGE AllowAmbiguousTypes -#}
import Frames
import Data.Singletons.Prelude
f' :: forall rs1 rs2 a. (AllAre a (UnColumn rs1), AllAre a (UnColumn rs2), Num a)
=> SList rs1 -> SList rs2 -> Frame (Record rs1) -> Frame (Record rs2) -> Int
f' = undefined
f df1 df2 = withSing (withSing f') df1 df2
。
(AllAre a0 (UnColumn rs1), AllAre a0 (UnColumn rs2))
事实上,根据GHCi(嗯,Intero),这正是推断的类型签名。根据我的理解,添加与推断签名匹配的显式签名应该对代码语义没有影响,那么为什么会破坏代码?
答案 0 :(得分:3)
作为一般经验法则,将与推断类型匹配的显式类型签名添加到Haskell程序中不会改变其含义,但在一般情况下实际上并未保证。 (我相信 保证Haskell98中的顶级定义。)
最终,您的问题与Haskell98中本地定义可能发生的类型变量范围问题的类型大不相同:
import Data.List
sortImage :: Ord b => (a -> b) -> [a] -> [a]
sortImage f = sortBy cmp
where cmp x y = compare (f x) (f y)
此处,cmp
的推断类型实际上是(Ord b) => a -> a -> Ordering
。但是,您无法明确签名,因为您无法将a
和b
绑定到外部签名(特别是f
的类型)除非你使用ScopedTypeVariables
,在这种情况下你可以写:
sortImage :: forall a b . Ord b => (a -> b) -> [a] -> [a]
sortImage f = sortBy cmp
where cmp :: a -> a -> Ordering
cmp x y = compare (f x) (f y)
正如您所发现的那样,您可以使用顶级定义来实现此类型变量范围问题,至少启用AllowAmbiguousTypes
。
这是一个更简单的例子,说明我认为是同一个问题,改编自AllowAmbiguousTypes
扩展名的GHC文档:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
class D a b
instance D Bool b
instance D Int b
strange :: D a b => a -> a
strange = undefined
-- stranger :: (D a b1, D a b) => a -> a
stranger x = strange (strange x)
我已将stranger
的推断类型显示为评论。如果您尝试明确说明,则会收到错误:
•由于使用'奇怪'而无法推断(D a b0) 从上下文来看:(D a b2,D a b)
问题在于,GHC可以推断stranger
可以在满足a
外D a b1
strange :: D a b1 => a -> a
的任何D a b
上调用,并且也可以满足strange :: D a b => a -> a
内在b1
。
但是,如果您尝试明确显示此类型签名,则b
的显式签名中的stranger
和strange
变量之间的链接及其与{的类型的关系{1}}来电失去了,就像假设的a
签名中的b
和cmp
与{中的a
和b
之间的关系一样{1}}签名在第一个示例中丢失。
单独使用sortImage
并不足以解决此问题,因为除了约束之外,ScopedTypeVariables
的类型只是strange
并且不会直接引用{ {1}}。所以,你可以写:
a -> a
但您无法将b
和stranger :: forall a b1 b2 . (D a b1, D a b2) => a -> a
stranger x = (strange :: a -> a) ((strange :: a -> a) x)
与b1
来电的类型联系起来。您需要b2
来执行此操作:
strange
然后键入检查好了,你甚至可以打电话:
TypeApplications
没有任何类型的注释(这有点令人惊讶)。如果你有一个实例:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
class D a b
instance D Bool b
strange :: forall a b . D a b => a -> a
strange = id
stranger :: forall a b1 b2 . (D a b1, D a b2) => a -> a
stranger x = (strange @a @b1) (strange @a @b2 x)
但是,您需要明确在> stranger False
False
上使用instance D Int Double
:
stranger