模板 Haskell 做变量赋值说明

Template Haskell do variable assignment clarification

我正在看下面的 TH 代码,有几个问题用 -- 标记:

newtype C a = C ExpQ

unC (C x) = x   --what is the significance of this

clift1 :: ExpQ -> C t -> C a  --why are the C's here followed by t and a
clift1 g (C x) = C $ do f <- g  --is the g an ExpQ? What is the f here, what does it signify?
                        tx <- x --what is the tx here signify?
                        return $ AppE f tx  

正如标题所暗示的,我特别不确定ftx来自哪里以及它们在这里代表什么。我认为它们只是被赋值的变量,以便它们可以用作 AppE 的参数,这是正确的吗?但在这种情况下,为什么?为什么不简单地使用这些参数调用一个函数而不是整个 clift1 do 函数?

ExpQ 是一个 monadic 值(在 Q monad 中),它给出了一些 Haskell 表达式的模板 Haskell 表示。底层表达式可以是任何类型,所以你可以有一个 ExpQ 代表一个布尔表达式,你可以使用 TH quotation:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH

boolExpr :: ExpQ
boolExpr = [| True && False |]

或直接从 TH 基元构建它:

-- almost the same as "boolExpr" above
boolExpr' :: ExpQ
boolExpr' = infixE (Just (conE (mkName "True"))) 
                   (varE (mkName "&&")) 
                   (Just (conE (mkName "False")))

您还可以使用 ExpQ 表示整数表达式:

intExpr :: ExpQ
intExpr = [| 2 + length "abc" |]

请注意,boolExprintExpr 具有相同的类型 ExpQ,即使它们表示具有不同类型的基础 Haskell 表达式(即 BoolInt)。这是因为模板 Haskell 提供类型 Haskell 表达式的 非类型化表示

使用 Haskell 表达式的 类型表示 会很有帮助,其中表示的表达式的类型在表示。一个简单的方法是为 ExpQ 类型定义一个 newtype 同义词,它采用所谓的“幻影”类型参数:

newtype C a = C ExpQ

这定义了一个新类型(或者实际上是整个类型集合,C IntC Bool 等),其值只是 ExpQ 值包裹在 C 构造函数。这意味着我们可以定义:

typedBoolExpr :: C Bool
typedBoolExpr = C [| True && False |]

typedIntExpr :: C Int
typedIntExpr = C [| 2 + length "abc" |]

其中表达式的 ExpQ 表示使用基础表达式的类型进行注释。当然,没有什么能真正阻止我们写作:

badlyTypedBoolExpr :: C Bool
badlyTypedboolExpr = C [| 2 + length "abc" |]

实际表达式类型与类型注释不匹配,因此编译器不会强制我们类型注释的正确性,但如果我们在一些关键点上使用一点自律来强制自己的正确性点,类型系统可以帮助我们保持代码库的其余部分正确。

有了这个背景,我们可以解决您的问题:

  1. unC (C x) = x有什么意义?

    没什么,真的。它只是一个字段访问器,让您可以从带类型注释的 C SomeType 值中提取 C 构造函数,以获取底层未类型化的 ExpQ 。它本来可以更简洁地写成:

    newtype C a = C { unC :: ExpQ }
    

    您发布的代码片段中似乎甚至没有使用 unC

  2. 为什么 C 后面跟着 ta in:

    clift1 :: ExpQ -> C t -> C a
    

    clift1的目的是采用函数表达式的无类型表示和参数表达式的类型表示,return表示第一个参数应用的表达式的类型表示到第二个论点。例如:

    foo = clift1 (varE (mkName "not")) typedBoolExpr
    

    请注意,在此示例中,所需的类型注释是 Bool,因为这是基础表达式 not (True && False) 的类型。但是,一般来说,将函数应用于参数的结果将是其他类型。因此,clift1 准备采用函数表达式的无类型表示(假设它实际上具有类型 t -> a),将其应用于任何调用者指定类型的参数表达式的类型表示 t,并生成任何调用者指定类型的应用程序表达式的结果类型化表示 a.

    由于未在任何地方指定函数表达式的类型,因此就 clift1 而言,ta 类型是任意的。我们需要希望在使用 clift1 的地方,它以类型敏感的方式使用,例如通过将类型正确的类型签名附加到使用 clift1:

    的函数
    myNot :: C Bool -> C Bool
    myNot = clift1 (varE (mkName "not"))
    
  3. gExpQ吗? ftx是什么意思?

    大多数 TH 处理发生在 Q monad 中。它允许你做一些事情,比如在编译期间报告错误,生成唯一的名字,以及一堆其他的事情。 do 语法和 clift1 中所有这些额外变量的目的是在 Q monad 中构造应用程序表达式。显然,clift1 的作者不仅对什么构成信息丰富的函数名称有糟糕的认识,而且还是由对 TH 不熟练的人编写的。可以更简洁地写成:

    clift1' :: ExpQ -> C t -> C a
    clift1' f (C x) = C (appE f x)
    

    这清楚地表明它只是在 Q monad 中构造一个函数应用程序表达式,并使用 C 类型注释构造函数进行适当的包装和解包。

综上所述,clift1Q monad 中运行,以获取实际类型 [=49] 的函数的无类型表示(即类型 ExpQ 的值) =],将其应用于实际类型 t 的参数的类型化表示(即类型 C t 的值),并构造类型化表示(即类型 C a 的值]) 将函数应用于实际类型为 a.

的参数