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
这解释了 add2
的 TyVar "a"
参数。
此外,Haskell 必须根据参数 a
和 b
是。它通过为每个类型 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") ...
)。
为了更好地研究 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
这解释了 add2
的 TyVar "a"
参数。
此外,Haskell 必须根据参数 a
和 b
是。它通过为每个类型 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") ...
)。