镍:以更少的成本实现更好的配置

2020-10-22 23:02:44

我们正在将镍库公之于众。镍是Tweag开发的一种实验配置语言。虽然现在还不是第一次租赁的时候,但这是一个谈论这个项目的机会。这篇文章的目标是对该项目进行一个高层次的概述。如果你的好奇心很强,但你还想学更多,那就不要害怕,因为我们将来会发布更多关于英语具体方面的博客文章。但是现在,让我们来游览一下吧!

[免责声明:Nickel的实际语法仍在研究中,出于说明的目的,我自由使用目前尚未存在的语法。但是,已经支持底层功能。]。

在Tweag,我们是NIX包管理器的忠实用户。事实证明,NIX的配置语言(也称为NIX)是一种非常好的配置语言,它不仅适用于包管理,还适用于更多的事情。

总而言之,Nix语言是一种带函数的懒惰JSON。它很简单,但功能强大。它用于生成NIX的包描述,但非常适合编写任何类型的配置(Terraform、Kubernetes等…)。。

问题在于,Nix-the-language的解释器与Nix-the-Package管理器紧密耦合。因此,按照目前的情况,将NIX语言用于包管理以外的任何事情都是一项非常痛苦的工作。

我们试图回答这个问题:如果Nix-the-language从包管理器中分离出来,它会是什么样子?同时抓住机会稍微改进一下语言,以Nix社区多年来的经验为基础。

NICI是一种轻量级通用配置语言。因为它可以取代YAML作为应用程序的配置语言。不过,与YAML不同的是,它通过可编程实现了大型配置。使用Nickel的另一种方式是生成静态配置文件(例如JSON、YAML),然后将其提供给另一个系统。像NikeNix一样,它被设计成有一个简单的、易于理解的核心:实际上,它是带有函数的JSON。

但过去使用Nix的经验也带来了一些关于这种语言的哪些方面可以改进的见解。无论一种语言的初始范围是什么,它几乎肯定会以一种与原始计划不同的方式使用:您创建了一种配置语言来描述软件包,接下来您知道,需要有人实现拓扑排序。

Niel努力保持Nix的简单性,同时根据这个反馈对其进行扩展,不过,您可以在没有新特性的情况下做得非常好,只需编写类似Nix的代码即可。

在这一点上,您可能会想这是不是已经在其他地方做过了,似乎每天都有越来越多的语言诞生,并且肯定已经存在了与Nickel具有类似用途的配置语言:Starlark、Jsonnet、DHall或CUE,不一而足。那为什么是镍呢?

也许与其他配置语言最重要的区别在于Nickel的键入方法。

一些语言,如Jsonnet或Starlark,不是静态类型的。实际上,静态类型在配置语言中可以被视为多余的:如果您的程序只在固定输入上运行一次,那么无论如何都会在运行时报告任何类型错误。为什么要为静态类型系统而烦恼呢?

另一方面,越来越多的系统依赖于复杂的配置,如云基础设施(Terraform、Kubernetes或NixOps),导致相应的程序变得越来越复杂,以至于静态类型是有益的。对于可重用的代码-也就是说,库函数-静态类型添加结构,作为文档,并及早消除错误。

DHall有一个功能强大的打字系统,可以打字范围很广。但它很复杂,需要一些经验才能变得流利。

CUE更接近我们的奋斗目标。它有一个可选的、行为良好的系统,有很强的保证。作为交换,人们通常不能编写Nortype高阶函数,即使一些简单的函数可以编码。

渐变类型并不引人注目:它们使你可以对程序的可重用部分进行静态类型,但是你仍然可以自由地编写没有任何类型的配置。解释器安全地处理类型化和非类型化世界之间的交互。

//文件:mylib.ncl{numToStr:Num->;Str=Fun n=>;...;makeURL:Str->;Str->;Num->;Str=Fun原始主机端口=>;";${proto}://${host}:${numToStr port}/";;}。

//file:server.ncllet mylib=import";mylib.ncl";input host=";myproject.com";in{host=host;port=1;urls=[mylib.makeURL";myproto";host port,{protocol=";proto2";;server=";sndserver.net";;port=4242}];}。

在第一个代码片段中,对numToStr和makeURL的主体进行了统计检查:错误地在makeURL内调用numToStr proto会引发错误,即使从未使用过makeURL。另一方面,第二个代码段没有注释,因此没有进行静态检查。特别地,我们在同一列表中将表示为字符串的URL与表示为记录的URL混合在一起。相反,解释器会插入运行时检查或契约,例如,如果make URL被误用,则程序会失败,并出现相应的错误。

渐进式类型还让我们保持类型系统的简单:即使是内部类型的代码,如果您要编写类型检查器不知道如何验证的组件,也不必对该部分进行类型检查。

作为静态类型系统的补充,Nickel提供合同。Contractsoffer精确而准确的动态类型错误报告,即使在存在函数类型的情况下也是如此。Nickel的解释器在内部使用合约在类型块和非类型块之间插入保护。合同也可供程序员使用,使他们能够以一种简单的方式在运行时强制执行类型断言。

这种设计的一个令人愉快的结果是,用户接触该类型系统的过程可以是渐进的:

编写配置的用户可以只编写类似Nix的代码,而忽略(几乎)所有关于键入的内容,因为您可以从非类型化代码无缝调用typedfunction。

基本计算块是函数,而Nickel中的基本数据块是记录(或JSON中的对象)。Ni支持编写自记录记录模式,例如:

{host|type:str|description:";|default:";faulback.myserver.net";;port|type:num|description:";|default:4242;url|type:url|description:";;}。

每个字段都可以包含元数据,如描述或默认值。它们的目的是在文档中显示,或通过工具查询。

然后可以将该架构用作合同。假设一个函数在其输出中设置了两个值并返回:

没有类型,就很难捕捉到这一点。当然,错误最终会在管道的下游弹出,但如何以及何时出现错误呢?使用上面的模式可以确保,无论何时实际计算字段,函数都会被归咎于类型错误。

模式实际上是涉及将记录合并在一起的更大故事的一部分,尤其是让模式用缺省值实例化缺少的字段。它非常受NixOS模块系统和CUE语言的启发,但这是另一个时代的故事。

我希望我能让你们对Nickel想要实现的目标有个了解。我只介绍了它最突出的方面:它的渐进型系统和合同,以及内置的记录模式。但是还有更多需要探索的地方!该语言还没有准备好在现实世界的应用程序中使用,但是这里展示的设计的很大一部分已经实现。如果你对它很好奇,那就去看看吧!