为什么美元($)运算符在GHC 8.0.1中如此复杂?

时间:2016-08-27 22:19:10

标签: haskell

Prelude> :i ($)
($) ::
  forall (r :: GHC.Types.RuntimeRep) a (b :: TYPE r).
  (a -> b) -> a -> b
        -- Defined in ‘GHC.Base’
infixr 0 $

(a -> b) -> a -> b有什么不同?是否有任何b不符合新类型签名?

1 个答案:

答案 0 :(得分:6)

在8.0之前,在类型检查器中有一个特殊情况,即将$应用于未提升的类型的工作。这也意味着您无法定义自己的功能,这些功能可以同时适用于提升和未提升的类型。现在这个所谓的Levity Polymorphsim('levity'指的是'解除某种东西的程度' - 或'提升',因为'未提升'和'提升'类型)被内置到类型检查器中,这是可能的:

import GHC.Exts (TYPE, RuntimeRep(..)) 
import Data.Kind (type (*))

ap :: forall (a :: *) (b :: *) . (a -> b) -> (a -> b) 
ap f x = f x 

ap_LP :: forall (a :: *) (b :: TYPE r) . (a -> b) -> (a -> b) 
ap_LP f x = f x

实际上$函数现在与ap_LP的定义完全相同,在类型检查器中不需要特殊情况使$与返回未提升类型的函数一起工作(还有一个在typechecker中进行多态应用的特殊情况,即runST $ ...工作,但这与levity多态无关)。这实际上是增加复杂性的原因 - 现在类型系统中的'黑客'更少,而GHC的用户只需通过给出一个适当类型的函数就可以利用levity多态性(请注意,levity-polymorphic类型永远不会被推断出来) ,据我所知)。在levity多态之前,如果你想编写一个可能对提升和未提升类型都有效的多态函数,你有义务用不同的类型签名写两个相同的函数副本。

新类型与旧类型的不同之处在于新类型比旧类型更为通用:

-- ok 
ap' :: forall (a :: *) (b :: *) . (a -> b) -> (a -> b) 
ap' = ap_LP 

-- type error: 
--    * Couldn't match a lifted type with an unlifted type
ap_LP' :: forall (a :: *) (b :: TYPE r) . (a -> b) -> (a -> b) 
ap_LP' = ap 

换句话说,“适合”旧签名的每个b必须(根据定义)适合新类型签名(因此此更改完全向后兼容!)。

另请注意,以下可能:

ap'' :: forall (a :: TYPE r) (b :: *) . (a -> b) -> (a -> b)
ap'' f x = f x 

产生的错误是

A representation-polymorphic type is not allowed here:
  Type: a
  Kind: TYPE r
In the type of binder `x'

和SPJ解释了限制的原因here

  

($)的第二个参数必须没有   没有装箱的。因为($)的代码必须移动该参数   (传递给函数),所以它必须知道它的宽度,指针等。

     

但实际上调用(f $ x)的结果是可以的   unboxed,因为($)的代码不会弄乱结果;它   只是尾巴调用f。

这就是说并非每个levity-polymorphic类型都有一个有效的居民 - 这与未装箱和盒装类型之间的操作区别有关,在某些情况下只能统一对待,而typechecker会确定它。