2024-02-05更新。
草稿
编写一个模板
在第一部分和第二部分中,你看到了Pkl提供了我们配置的验证。它检查语法、类型和约束。正如你在acmecicd示例中看到的,当一个修订模块违反了类型约束时,模板可以提供信息性的错误信息。在这最后一部分中,你将看到一些特别适用于编写模板的Pkl技术。
基本类型
Pkl总是检查其输入的语法。在评估您的配置时,它还会检查类型。您已经见过对象、列表和映射了。这些提供了编写结构化配置的方法。在您能为它们编写类型之前,您需要知道如何为最简单的(非结构化)值编写类型。以下是Pkl的所有基本类型:pklTutorialPart3.pkl
笔记
name: String = "Writing a Template" part: Int = 3 hasExercises: Boolean = true amountLearned: Float = 13.37 duration: Duration = 30.min bandwidthRequirementPerSecond: DataSize = 52.4288.mb
在上面的例子中,你已经明确地使用类型签名对代码进行了注解。Pkl的默认输出实际上是pcf,它是Pkl的一个子集。由于pcf没有类型签名,运行Pkl在这个例子中会移除它们。
笔记
$ pkl eval pklTutorialPart3.pkl name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb
注意持续时间和数据大小如何帮助你在这些常见的(对于配置而言)领域中避免单位错误。
Typed objects, properties and amending 翻译成中文是:类型化的对象、属性和修订
拥有了基本类型的符号表示,现在你可以编写带类型的对象。simpleClass.pkl
笔记
class Language { ① name: String } bestForConfig: Language = new { ② name = "Pkl" }
① 一个类定义。② 使用Language类的属性定义。注释 虽然不是要求(或强制性的),习惯上使属性名以小写字母开头。按照同样的传统,类名以大写字母开头。你可以用类来给对象进行类型注解。在这个例子中,你定义了一个叫做Language的类。现在你可以确信,每个Language的实例都有一个类型为String的属性name。类型和值在Pkl中是不同的东西。Pkl不会在它的输出中渲染类型,[1] 因此当你在Pkl上运行这个时,你根本看不到类定义。
笔记
$ pkl eval simpleClass.pkl bestForConfig { name = "Pkl" }
你有没有注意到,输出不仅省略了类型签名,而且也省略了 = new?我们将在下一节中进一步讨论。当你的配置描述了像这样的几个不同部分时,你可以定义一个实例,并为其他每个实例修改它。例如:pklTutorialParts.pkl
笔记
class TutorialPart { name: String part: Int 2 hasExercises: Boolean amountLearned: Float duration: Duration bandwidthRequirementPerSecond: DataSize } pklTutorialPart1: TutorialPart = new { name = "Basic Configuration" part = 1 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 50.mib.toUnit("mb") } pklTutorialPart2: TutorialPart = (pklTutorialPart1) { name = "Filling out a Template" part = 2 } pklTutorialPart3: TutorialPart = (pklTutorialPart1) { name = "Writing a Template" part = 3 }
您可以把这句话理解为“pklTutorialPart2 和 pklTutorialPart3 与 pklTutorialPart1 完全相同,除了他们的名称和部分不同。”运行 Pkl 确认了这一点:
笔记
$ pkl eval pklTutorialParts.pkl pklTutorialPart1 { name = "Basic Configuration" part = 1 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb } pklTutorialPart2 { name = "Filling out a Template" part = 2 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb } pklTutorialPart3 { 3 name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb }
遗憾的是,pklTutorialParts.pkl 是 pklTutorial.pkl 的一次重写。它创建了一个单独的类 TutorialPart,并用它实例化了三个属性(pklTutorialPart1、pklTutorialPart2 和 pklTutorialPart3)。这样一来,它隐式地将所有内容“下移”了一层(pklTutorialPart3 现在是模块 pklTutorialParts 中的一个属性,而在上面,在 pklTutorialPart3.pkl 中,它是自己的模块)。这并不是非常符合DRY(不重复自己)原则。事实上,你不需要这样的重写。任何 .pkl 文件在 Pkl 中定义了一个模块。任何模块都由一个模块类表示,这是一个实际的 Pkl 类。模块和其他类并不完全相同,因为 Pkl 从不在输出中渲染类定义。然而,当你在 pklTutorialPart3.pkl 上运行 Pkl 时,它确实产生了一个输出。这是因为模块还定义了模块类的一个实例。在模块中(或在任何“正常”类中)赋予属性的值被称为默认值。当你实例化一个类时,所有你没有提供值的属性都从该类的默认值中获取。在我们示例中的教程部分,只有名称和部分在实例间是不同的。你可以通过在(模块)类定义中添加默认值来表示这一点。你可以定义模块 tutorialPart 而不是从特定的教程部分开始,如下所示:TutorialPart.pkl。
笔记
name: String ① part: Int ① hasExercises: Boolean = true ② amountLearned: Float = 13.37 ② duration: Duration = 30.min ② bandwidthRequirementPerSecond: DataSize = 52.4288.mb ②
① 没有给定默认值。② 给定了默认值。将这个通过Pkl运行会出错,当然,是因为缺失值:
笔记
$ pkl eval TutorialPart.pkl –– Pkl Error –– Tried to read property `name` but its value is undefined. 4 1 | name: String ^^^^ ...
现在个别部分只需要填补缺失的字段,因此你可以更改 pklTutorialPart3.pkl 来修改这个:pklTutorialPart3.pkl
笔记
amends "TutorialPart.pkl" name = "Writing a Template" part = 3
这导致了
笔记
$ pkl eval pklTutorialPart3.pkl name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb
这现在的行为和我们的pklTutorialPart3完全一样:TutorialPart = (pklTutorialPart1) {…之前的内容。pklTutorialPart3现在被定义为我们通过修改tutorialPart并为其命名和指定部分所得到的值。重要提示:修改任何事物从不会改变它的类型。当我们修改Foo类型的对象时,结果将始终严格是Foo类型。所谓"严格",我们的意思是,修改一个对象也不能使它"变成"被修改对象的类的子类的一个实例。
一个新模板
现在你已经了解了类型,可以开始编写你的第一个模板了。到目前为止,你已经使用Pkl编写了配置文件,要么没有使用模板,要么使用Pkl Hub上的模板。通常最简单的方式是先为你想要创建模板的(典型)配置编写一个示例。假设你想定义这个教程的在线研讨会应该是什么样的。考虑下面这个例子:workshop2023.pkl
笔记
title = "Pkl: Configure your Systems in New Ways" interactive = true seats = 100 5 occupancy = 0.85 duration = 1.5.h `abstract` = """ With more systems to configure, the software industry is drowning in repetitive and brittle configuration files. YAML and other configuration formats have been turned into programming languages against their will. Unsurprisingly, they don’t live up to the task. Pkl puts you back in control. """ event { name = "Migrating Birds between hemispheres" year = 2023 } instructors { "Kate Sparrow" "Jerome Owl" } sessions { new { date = "8/14/2023" time = 30.min } new { date = "8/15/2023" time = 30.min } } assistants { ["kevin"] = "Kevin Parrot" ["betty"] = "Betty Harrier" } agenda { ["beginners"] { title = "Basic Configuration" part = 1 duration = 45.min } ["intermediates"] { title = "Filling out a Template" part = 2 duration = 45.min } ["experts"] { title = "Writing a Template" part = 3 6 duration = 45.min } }
将你的新模板命名为Workshop.pkl。虽然不是必需的,但始终用模块-条款对你的模板进行命名是一个很好的实践。定义前几个属性就像你在前一节看到的那样:
笔记
module Workshop title: String interactive: Boolean seats: Int occupancy: Float duration: Duration `abstract`: String
与前几个属性不同,event是一个具有多个属性的对象。要能够键入event,你需要一个类。你之前已经看到过如何定义这个:
笔记
class Event { name: String year: Int } event: Event
接下来,指导员并不是一个具有属性的对象,而是一个由未命名值组成的列表。Pkl为此提供了列表类型。
笔记
instructors: Listing<String>
sessions是对象的列表,所以你需要一个Session类。
笔记
class Session { time: Duration date: String } 7 sessions: Listing<Session>
助手有一个类似对象的结构,所有的值都有命名,但是并非所有可能的工作坊的名称集合都是固定的(并且某些工作坊可能比其他工作坊有更多的助手)。对应的Pkl类型是映射。
笔记
assistants: Mapping<String, String>
最后,对于每个研讨会环节,都有一个议程,其中描述了涵盖了哪些教程部分。你已经定义了TutorialPart.pkl作为它自己的模块,所以你不应该定义一个单独的类,而是应该导入那个模块并在这里重用它:
笔记
import "TutorialPart.pkl" ① agenda: Mapping<String, TutorialPart>
这条import语句将TutorialPart这个名字引入了作用域,这是如上所述的模块类。注意import语句必须出现在属性定义之前。综合起来,你的Workshop.pkl模板看起来像这样:Workshop.pkl。
笔记
module Workshop import "TutorialPart.pkl" title: String interactive: Boolean seats: Int occupancy: Float duration: Duration `abstract`: String class Event { name: String year: Int } event: Event instructors: Listing<String> 8 class Session { time: Duration date: String } sessions: Listing<Session> assistants: Mapping<String, String> agenda: Mapping<String, TutorialPart>
尽管某些输出格式可以包含它们自己的类型注释形式。这可能源自Pkl类型。类型定义(类和类型别名)本身从不会被渲染。