配置语言的五个层级 (The 5 Levels of Configuration Languages)

代码即数据,数据即代码。多年前,我和 Lisp 有过短暂的接触,并从中领悟了这个概念。今天,我相信分离代码和数据也有好处。

每当人们讨论另一种配置模式的语法时,这种争论就会浮出水面。配置语言有五个层级的能力。如果您要设计一个新的模式,请了解所有这些层级。

层级 1:文件中的字符串

文件系统是一个键值存储。Linux 内核通过 procfssysfs 来实现这一点。以下是我在 shell 中的示例:

$ cat /proc/sys/kernel/arch
x86_64
$ cat /proc/sys/kernel/sched_energy_aware
1

我可以将 0 写入第二个文件来更改内核行为。

这当然是最简单的格式,但它确实有效。

层级 2:列表

为了获得更强的表达能力,您可以将文件内容视为列表。也许每行一个。也许每行一个键值对。也许像 INI file 一样带有节。示例文件内容:

[database]
server = 192.0.2.62   
port = 143
file = "payroll.dat"

这已经足够复杂,以至于并非所有内容都是直观的。如果重复一个键会发生什么?您可以进行多行字符串操作吗?

定义约束是您不能有列表的列表。那将是下一个层级。但是,在进入该层级之前请三思,因为只需添加一些前缀和后缀名称,您就可以在这里做很多事情。

层级 3:嵌套数据结构

这可能是最流行的层级,我们可以在这里找到 JSON, YAML, XML, TOML 等。示例文件内容:

{
 "database": {
  "host": "localhost",
  "port": 1234,
  "auth": {
   "user": "elon",
   "password": "mars2023"
  }
 }
}

令人着迷的是,人们可以就这个层级上替代方案的优缺点进行多少讨论,即使它们或多或少是相同的。

我实际上喜欢 XML。它不像 YAML 那样“酷”了,但它具有更好的工具支持(例如,模式检查),并且不会试图 过于聪明。尽量避开命名空间,并且不要害怕使用属性。

在实践中,许多人后来遇到了无法计算任何东西的限制。也许他们需要变量,或者想要生成一个事物列表。然后,他们使用诸如“作为值的 Python 表达式”或“用于生成的 Jinja 模板”之类的可憎之物对其进行改造。在这一点上,你最好上升一个层级,这就是我们从数据过渡到代码的地方,你不觉得吗?

层级 4:完全编程语言

这是最不为人所知的层级,并且可能应该更受欢迎。完全函数式编程 意味着您可以计算东西,但它会终止。这明确地不是图灵完备的。

这个层级包括 XSLT, Jsonnet (一个 JSON 扩展),甚至包括像 Dhall 这样的类型语言。 这是一个来自 Bazel 的 Starlark 示例:

java_binary(
  name = "ProjectRunner",
  srcs = glob(["src/main/java/com/example/*.java"]),
)

这里的挑战在于你正在编程,但是由于这些语言不是很流行,因此你没有可用的常用语言工具。所以,最终的层级...

层级 5:完整编程语言

当然,你可以使用任何脚本语言来配置事物。Python, Javascript, Lua, TCL,等等。它们是图灵完备的。例如,Conan 是一个包管理器,你可以在 Python 中指定包:

from conan import ConanFile
class CompressorRecipe(ConanFile):
  settings = "os", "compiler", "build_type", "arch"
  generators = "CMakeToolchain", "CMakeDeps"
  def requirements(self):
    self.requires("zlib/1.2.11")
  def build_requirements(self):
    self.tool_requires("cmake/3.22.6")

任何 Python 程序员都可以轻松地在他们认为合适的地方添加复杂的逻辑。

人们经常发现这个问题,即配置决定了导入的内容,但是导入也决定了配置本身。这种循环依赖会导致疯狂。

例如,在 Conan 中,你像上面的示例一样声明依赖项。你可能想要依赖于你在此脚本中使用的某个 Python 模块。但是,此时你已经在执行该脚本了。因此,Conan 发明了 python_requires_extend,这是它自己将超类注入到现有对象中的怪异方式。

如何避免这种疯狂?引入另一个低级别的配置文件。回到第一层……

应该使用哪个层级?

指导原则是使用尽可能低的层级以使其保持简单。不幸的是,这通常不是一个容易的决定,因为你不知道未来会怎样。

我的层级结构的必然结果是:不要浪费时间在同一层级内的讨论上。例如,JSON 和 YAML 都有它们的问题和陷阱,但是两者可能都足够好。