【译文】蒙特利尔效应:编程语言为何需要风格沙皇

下面是一个非现实的场景:您正在为最终将成为大型项目的项目选择编程语言。想象一下在 mono repo 中的服务集合,有 100 多人在为其工作。为了保持这种额外的不现实,我们假设你忽略了通常的限制因素,比如你是否负担得起 GC,或者这个问题是否适合特定的技术栈等等。幽默一下吧。

根据我之前的文章,你会正确地认为我想要一种针对专家的表达式语言。的确如此。但如果使用灵活的语言来扩展,就会出现一个大问题。编码风格太多,编程方式太多。你最终需要风格指南来确定正确的方法。

你使用的是 C++ 或 Kotlin 的哪个子集?你使用的是 project.toml 还是 requirements.txt?你的语言现在有了类型注解(Type Annotations)的渐进式类型。你到底要不要采用?您打算使用多线程、Tokio 还是 Async-std 来实现并发?

语言的表现力越强,就越难做到这一点。这正是 Go 的优势所在。它不仅仅是 gofmt,还有它的标准库和一致的做事方式。在 Kotlin 中,你会纠结:异常还是错误结果?但在 Go 中,你知道该怎么做。查找 err。当然,这很啰嗦,但它是可预测的。

表现力强的语言固然好,但也可能很混乱。你可以拥有一门丰富而复杂的语言,却不需要一百万种做同一件事的方法。这就是我想向大家展示的。我们怎样才能既保持强大的功能,又避免杂乱无章?如何避免出现 500 种语言的子方言?在深入探讨解决方案之前,我们先来谈谈 Scala。

Scala 的问题

为了突出这个问题,我们以 Scala 为例。顺便说一句,我非常喜欢这门语言。但它有一个大问题。Scala 没有惯用语。它太灵活了。

我可以写一个文件、一个计算器类,然后以 Java 风格开始:

 // Returns, braces and semi-colons
    def getResult(): Double = {
      return result;
    }
    
    def multiply(number: Double): Calculator = {
      if (number == 0) { 
        println("Multiplication skipped: number is 0"); 
      } else { 
        result = result * abs(number); 
      }
      return this;
    }

同样的类,同样的文件,我可以切换到伪 Python 风格:

    // significant whitespace, no returns, no semi-colons
    def add(number: Double): Calculator = 
      result += abs(number)
      this

    def subtract(number: Double): Calculator =
      result -= abs(number)
      this

然后,当我调用整个程序时,就可以使用无大括号和无点样式。Ruby DSL 风格:

val calc = new Calculator add -5 subtract -3 multiply -2

Full Thing.

希望没有人会被这样的代码困住。你选择了自己的 Scala 方言,并坚持使用。但随着代码的增长,就像蒙特利尔一样。城市的每个角落都是不同的。在足够长的时间尺度上,编程语言中可能出现的每一个怪癖都会在你的代码中显现出来。

最终,有人会复制并粘贴不同风格的代码。也许他们更喜欢这种风格。或者在一项新服务中,他们按自己的方式行事。或者,一个初学者从文档中模仿了一个库的风格。风格分歧就开始了1。

(hn上的每个Scala主题都有这样的评论:有人继承了一个Scala代码库,但却很难理解它,部分原因是它采用了外来风格。)

蒙特利尔 C++ 问题

C++20 有很多好的想法,但很多代码都是在该标准之前编写的。于是,漂移就出现了。要么不采用新方法,要么最终代码库中会出现多种风格。如果采用后者,最终就会出现蒙特利尔问题。如果你在旧蒙特利尔代码区工作,就会遇到这个问题。这就像是不同的方言。您现在需要了解语言的多种方言,以及何时何地应用每种方言。

那么,如何在不分裂语言的前提下发展语言呢?有了整个社区的参与,这个问题就变得更加棘手了。大型开源项目通常都有自己的风格。分歧的自然趋势意味着,我们很难跳进我们并不熟悉的现有代码库,因为它们有自己的风格。这样一来,社区就会分裂。

仅有风格指南是不够的

风格指南,尤其是当它们可以由机器强制执行时,可以在大型代码库的层面上提供很大帮助。我认为,如果语言可以进行实验,那将是一件很棒的事情。也许我们还不知道在 Python 中到处使用类型是否完全合理,或者在 Go 或其他语言中应该使用多少泛型。

但对于一个特定的项目,我们可以设定规则。比如说,”这个 Python 需要类型 “或 “尽可能在 Go 中使用泛型”。我们为测试库和构建工具设定了标准。在可能的情况下,我们会尝试通过工具来执行这些规则。但我认为,在语言社区层面,我们可以做得更好。

仅有代码库特定的风格指南是不够的。

风格沙皇

2006 年 Scala 2.0 发布时,内部 DSL 风靡一时,在 Ruby on Rails 的引领下,Scala 开始采用更流畅的编写风格。但时过境迁,这种风格已经不再是惯用的 Scala 了。

但如果你不在正确的圈子里,你怎么会知道呢?大型 ORM 框架仍然在其指南中使用这种风格。编写现代惯用 Scala 的技巧被困在社区领导者的头脑中。这可不好。

我们需要一个风格沙皇。语言社区中的某个人可以说这就是习语化的 Scala 2.1

def ABSOrSeven(maybeNumber: Option[Int]): Int = {
  if (maybeNumber.isDefined) Math.abs(maybeNumber.get)
  else 7
}

但在 Scala 3.1 中,这是首选:

def ABSOrSeven(maybeNumber: Option[Int]): Int = {
  maybeNumber.map(Math.abs).getOrElse(7)
}

我的建议是,每一种语言的发布都应附带一份风格指南。没有人必须遵循它;公司和项目可能会有分歧,标准可能会有很大争议;但它应该存在,并写在某个地方。

Idiomatic Python

Python 用户喜欢他们的 Pythonic 代码、Python 的禅宗和 PEP 8。这就是我的想法,但会随着时间的推移而不断发展,而且范围会更大。我认为语言的创造者需要做的不仅仅是创造语言,还要在如何使用语言编程方面成为新兴标准的守护者。他们需要与我们对话,告诉我们:要这样做,不要那样做。这应该是一场对话,是一场辩论,是帮助我们遵守规则的工具。

这个标准会不断变化,对吗?它会随着语言的发展而发展。也许类型注解刚在 Python 中推出时,被认为是实验性的。但一旦每个人都习惯了它们,并认为它们是个好主意,Python 就应该表明态度,说类型注解是 Python 代码的必需。或者说不需要。但请发表意见。随着 Python 语言的发展和社区开始出现各种分歧,样式文档的范围也应该随之扩大。

这里有一个例子:Python 必须在软件包管理器和虚拟环境方面选择一条道路。我认为诗歌和 project.toml 应该是未来的方向,其他人则有一个 requirements.txt 终身纹身。但任何解决方案都比我们现在拥有的要好。

我们需要有负责人站出来。”好了,各位,我们将使用 Hatch 作为 Python 打包的标准。如果你们对 Hatch 有意见,请说出来。我们会调查的。我们的目标是让 Hatch 成为 Python 3.16 的首选。”

这适用于测试框架、标准库,甚至我们处理并发的方式。语言社区喜欢实验和探索。但在探索之后,我们需要团结起来。而这正是语言创建者的职责所在。只有他们才能真正实现这一点。

表现力

好的,那么这与表现力有什么联系呢?如果您的语言是 “专家级可读性”,那么您可能拥有大量的功能,并且不怕添加新的功能。问题是,你会慢慢陷入这样一个世界:每个代码库都是用自己的语言子集编写的。所以,要废弃一些东西。当然,为了兼容性,可以保留旧的东西,但让我们把每个人都推向一个共同的标准。

你在发展语言,这意味着你对优秀代码的样子有自己的看法。告诉我们吧!写下来。与社区一起讨论。在 C++20 中,使用宏不是习语。使用 if 来检查返回对象的类型在 Kotlin 1.17 中不是习语。在 Scala 中不要使用显式返回。等等。

这样,优秀代码就有了一个目标。即使目标移动了,每个正常的代码库也只是在通往最新、最伟大代码风格的旅程中的一个特定点。

换句话说,你可以最终实现一个所有惯用的 Java 20 代码都像 Go 一样统一的世界,但要实现这一点,你必须对何时适合使用流 API、何时不适合使用流 API 采取立场。我的意思是,也许你永远无法达到这样的境界:一个人清晰的流处理单行代码就是另一个人的意大利面代码,但我真的认为,我们可以做得更好,让我们的语言趋于一致。

结束黄油之争

这带来了一堆问题。在多大程度上可以通过工具强制执行?什么时候流行的图书馆应该被规范化?多少风格指导才算过多,才会扼杀创新?我真的不知道。就从 Python PEP 8 的一个版本开始吧,随着时间的推移不断发展和扩展。

如果某些东西是社区规范,就把它写下来。如果社区在争论是黄油面朝上还是黄油面朝下吃吐司,那就掷硬币决定,然后继续前进。社区会因此变得更好。

为我们指明方向吧,风格沙皇!

1.这只是一个简单的示例。如果我是 “风格沙皇”,那么是的,”不要使用显式返回或分号 “将是一条规则。但是,如果可以使用 fold 或 map,就不要进行模式匹配;如果可以使用 getOrElse,就不要使用 fold;不要进行手动递归;除非有非常充分的理由,否则不要使用 actors;不要编写自定义操作符。就是不要。等等,不一而足。很多功能只有在特定情况下才有价值。

本文文字及图片出自 The Montréal Effect: Why Programming Languages Need a Style Czar

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注