[英]Type annotation breaks function
考虑以下使用框架框架(定义了UnColumn和AllAre)的单例函数f'的声明以及使用withSing的包装函数。
{-# 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))
。
f :: (SingI rs1, SingI rs2, AllAre a (UnColumn rs2), AllAre a (UnColumn rs1), Num a)
=> Frame (Record rs1) -> Frame (Record rs2) -> Int
f df1 df2 = withSing (withSing f') df1 df2
事实是,根据GHCi(好吧,Intero),这正是推断的类型签名。 据我了解,添加一个与推断出的签名相匹配的显式签名应该不会对代码语义产生影响,那么为什么要破坏代码呢?
作为一般经验法则 ,将与推断的类型匹配的显式类型签名添加到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
。 但是,您不能使该签名明确,因为除非使用ScopedTypeVariables
,否则您不能将a
和b
绑定到外部签名(特别是f
的类型),在这种情况下,您可以编写:
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 ab)
问题是,GHC可以推断, stranger
可以叫上任何a
即满足D a b1
的外strange :: D a b1 => a -> a
和也满足D ab
为内strange :: D ab => a -> a
。
但是,如果您尝试进行这种类型的签名明确的之间的联系, b1
和b
为显性特征变量stranger
和他们的类型的关系strange
来电丢失,多为关系a
和b
在假设的cmp
签名以及sortImage
签名中的a
和b
在第一个示例中丢失了。
仅使用ScopedTypeVariables
不足以解决这里的问题,因为除约束之外, strange
的类型只是ScopedTypeVariables
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
但是,那么您需要明确地在Int
上使用stranger
:
> stranger @_ @Double @Double (1 :: Int)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.