我为Core Haskell编写了一个自定义漂亮的打印机,以便更好地研究Core的结构。这个漂亮的打印机的要点是它需要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
或{{1}在接受参数之前。
对于$fNumInt
函数,也明确给出了类型,而add
由编译器推断。这似乎也会影响lambda函数链需要进行评估的参数数量,add2
需要2而add
需要4。
这一切意味着什么?
答案 0 :(得分:7)
核心非常SystemF(技术上SystemFC)。在SystemF中,类型变量也需要是函数的参数。在您的示例中,Haskell推断出
add2 :: Num a => a -> a -> a
add2 a b = a + b
这解释了TyVar "a"
的{{1}}参数。
此外,Haskell必须找到一种方法来根据add2
和Num
的参数类型调度到“正确”的a
函数集。它通过为每个类型类约束提供字典参数来实现。这是b
参数。在Id $dNum
的情况下,Haskell已经知道哪个字典可以找到适当的add
函数,因为它知道它知道操作在(+)
上(所以它不需要传递in:它只是Int
)。
基本上发生的事情是,对于每个类型类,Haskell使用字段作为类型类中的函数创建记录$fNumInt
。然后,对于每个实例,它会生成另一个data $d<Class> = ...
。 This is explained in more detail here
Here is another excellent answer describing Core related things.
答案 1 :(得分:5)
在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
采用隐藏的&#34;类型值&#34;参数n
(在发布的示例中混淆地称为a
,即(Lam (TyVar "a") ...
),然后作为类型参数转发给(+)
。由于字典现在是未知的,因此在Core中还有另一个隐藏的参数:字典必须由add2
的调用者传递,然后将其转发到(+)
。这个附加参数称为$dNum
(参见(Lam (Id "$dNum") ...
)。