Core Haskell 将类型应用于函数是什么意思?

What does Core Haskell applying types to functions mean?

为了更好地研究 Core 的结构,我为 Core Haskell 编写了一个自定义漂亮的打印机。这个漂亮的打印机的要点是它需要一个 CoreModule 并在输出中包含数据构造函数,默认的 Outputable 实现似乎没有这样做。

这是我运行漂亮打印机的模块代码:

module Bar2 where

add :: Int -> Int -> Int
add a b = a + b

add2 a b = a + b

这是漂亮的打印机输出:

------------------------------- Module Metadata --------------------------------
Module { "main" :: modulePackageId, "Bar2" :: moduleName }
-------------------------------- Type Bindings ---------------------------------
[r0 :-> Identifier ‘add’, rjH :-> Identifier ‘add2’]
-------------------------------- Core Bindings ---------------------------------
NonRec (Id "add2")
       (Lam (TyVar "a")
            (Lam (Id "$dNum")
                 (Lam (Id "a1")
                      (Lam (Id "b")
                           (App (App (App (App (Var (Id "+"))
                                               (Type (TyVar (TyVar "a"))))
                                          (Var (Id "$dNum")))
                                     (Var (Id "a1")))
                                (Var (Id "b")))))))

NonRec (Id "add")
       (Lam (Id "a")
            (Lam (Id "b")
                 (App (App (App (App (Var (Id "+"))
                                     (Type (TyConApp (Int) [])))
                                (Var (Id "$fNumInt")))
                           (Var (Id "a")))
                      (Var (Id "b")))))
--------------------------------- Safe Haskell ---------------------------------
Safe
------------------------------------- End --------------------------------------

让我感到困惑的是,在这两种情况下,Core 似乎都在将类型变量或类型构造函数应用于 + 函数,以及一些 $dNum$fNumInt 在接受论据之前。

对于add函数,类型也是明确给出的,而add2留给编译器推断。这似乎也影响了 lambda 函数链评估所需的参数数量,add 需要 2 个而 add2 需要 4 个。

这是什么意思?

核心差不多 SystemF (technically SystemFC)。在 SystemF 中,类型变量也需要作为函数的参数。在您的示例中,Haskell 推断出

add2 :: Num a => a -> a -> a 
add2 a b = a + b

这解释了 add2TyVar "a" 参数。

此外,Haskell 必须根据参数 ab 是。它通过为每个类型 class 约束设置一个字典参数来做到这一点。那就是 Id $dNum 参数。在 add 的情况下,Haskell 已经知道可以在哪个字典中找到合适的 (+) 函数,因为它知道它知道操作在 Int 上(所以它不知道需要传入:就是 $fNumInt).

本质上,在幕后发生的事情是,对于每个类型class,Haskell 都会创建一个记录data $d<Class> = ...,其中的字段是类型class 中的函数。然后,对于每个实例,它都会生成另一个 $f<Class><Type> :: $d<Class>This is explained in more detail here

Here is another excellent answer describing Core related things.

在 GHC 8.x 中,您也可以在 Haskell 中使用类型参数,类似于 Core。这是一个基于发布的代码的带有更多注释的示例。

add :: Int -> Int -> Int
add a b = (+) @ Int a b

(+) @ Int 专门化了多态 (+) 运算符,因此它适用于类型 Int

在 Core 中,您还会看到正在传递的类型类字典 $fNumInt

add2 :: forall n. Num n => n -> n -> n    
add2 a b = (+) @ n a b

这个基本一样,只是n不知道。

在 Core 中,add2 采用隐藏的 "type-valued" 参数 n(在已发布的示例中被混淆地称为 a,即 (Lam (TyVar "a") ...),它是然后作为类型参数转发给 (+)。由于字典现在是未知的,在 Core 中还有另一个隐藏的参数:字典必须由 add2 的调用者传递,然后将其转发给 (+)。此附加参数称为 $dNum(参见 (Lam (Id "$dNum") ...)。