[英]What does Core Haskell applying types to functions mean?
I wrote a custom pretty printer for Core Haskell in order to better study Core's structure. 我为Core Haskell编写了一个定制漂亮的打印机,以便更好地研究Core的结构。 The gist of this pretty printer is that it takes a CoreModule and includes data constructors in the output, which the default
Outputable
implementation does not seem to do. 这个漂亮的打印机的要点是它需要一个CoreModule并在输出中包含数据构造函数,默认的
Outputable
实现似乎没有。
Here is the code of the module that I am running the pretty printer on: 这是我运行漂亮的打印机的模块的代码:
module Bar2 where
add :: Int -> Int -> Int
add a b = a + b
add2 a b = a + b
Here is the pretty printer output: 这是漂亮的打印机输出:
------------------------------- 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 --------------------------------------
What is confusing to me is that in both instances, Core appears to be applying a type variable, or a type constructor to the +
function, as well as some $dNum
or $fNumInt
before taking in the arguments. 令我感到困惑的是,在两个实例中,Core似乎在接受参数之前将一个类型变量或类型构造函数应用于
+
函数,以及一些$dNum
或$fNumInt
。
For the add
function, the type is also explicitly given, while the add2
is left up to compiler inference. 对于
add
函数,还明确给出了类型,而add2
则留给编译器推断。 This also seems to affect the number of arguments that the chain of lambda functions requires for evaluation, with add
needing 2 while add2
requiring 4. 这似乎也会影响lambda函数链需要进行评估的参数数量,
add
需要2,而add2
需要4。
What does this all mean? 这是什么意思呢?
Core is pretty much SystemF (technically SystemFC ). Core几乎是SystemF (技术上是SystemFC )。 In SystemF, type variables also need to be arguments to the function.
在SystemF中,类型变量也需要是函数的参数。 In your example, Haskell infers that
在您的示例中,Haskell推断出这一点
add2 :: Num a => a -> a -> a
add2 a b = a + b
That explains the TyVar "a"
argument to add2
. 这也解释了
TyVar "a"
参数add2
。
Also, Haskell has to find a way to dispatch to the 'right' set of Num
functions depending on what the type of the arguments a
and b
is. 此外,Haskell必须找到一种方法来分配到'正确'的
Num
函数集,具体取决于参数a
和b
类型。 It does that by having a dictionary argument for each type class constraint. 它通过为每个类型类约束提供字典参数来实现。 That's the
Id $dNum
argument. 这是
Id $dNum
参数。 In the case of add
, Haskell already knows which dictionary the appropriate (+)
function can be found since it knows it knows the operation is on Int
(so it doesn't need to be passed in: it's just $fNumInt
). 在
add
的情况下,Haskell已经知道哪个字典可以找到适当的(+)
函数,因为它知道它知道操作在Int
(所以它不需要传入:它只是$fNumInt
)。
Essentially what happens under the hood is that for each typeclass Haskell makes a record data $d<Class> = ...
with fields that are the functions inside the typeclass. 本质上发生的事情是,对于每个类型类,Haskell创建一个记录
data $d<Class> = ...
其中字段是类型类中的函数。 Then, for each instance, it makes another $f<Class><Type> :: $d<Class>
. 然后,对于每个实例,它会生成另一个
$f<Class><Type> :: $d<Class>
。 This is explained in more detail here 这在这里有更详细的解释
Here is another excellent answer describing Core related things. 这是另一个描述Core相关事物的优秀答案。
In GHC 8.x you can play with type arguments in Haskell as well, similarly to Core. 在GHC 8.x中,您也可以在Haskell中使用类型参数,类似于Core。 Here's an example with some more annotations, based on the posted code.
这是一个带有更多注释的示例,基于发布的代码。
add :: Int -> Int -> Int
add a b = (+) @ Int a b
The (+) @ Int
specializes the polymorphic (+)
operator so that it works on type Int
. (+) @ Int
专门用于多态(+)
运算符,因此它适用于Int
类型。
In Core, you also see the typeclass dictionary being passed around $fNumInt
. 在Core中,您还可以看到在
$fNumInt
周围传递的类型类字典。
add2 :: forall n. Num n => n -> n -> n
add2 a b = (+) @ n a b
This is basically the same, except that n
is not known. 这基本上是相同的,除了
n
是未知的。
In Core, add2
takes a hidden "type-valued" argument n
(confusingly called a
in the posted example, ie (Lam (TyVar "a") ...
), which is then forwarded to (+)
as a type argument. Since the dictionary is now unknown, in Core there is another hidden argument: the dictionary has to be passed by the caller of add2
, which then forwards it to (+)
. This additional argument is called $dNum
(See (Lam (Id "$dNum") ...
). 在Core中,
add2
采用隐藏的“类型值”参数n
(在发布的示例中混淆地称为a
,即(Lam (TyVar "a") ...
),然后将其作为类型参数转发到(+)
。由于字典现在是未知的,在Core中还有另一个隐藏的参数:字典必须由add2
的调用者传递,然后将其转发到(+)
。这个额外的参数称为$dNum
(参见(Lam (Id "$dNum") ...
)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.