1

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

 1 month ago
source link: https://www.techug.com/post/why-programming-languages-need-a-style-czar/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

下面是一个非现实的场景:您正在为最终将成为大型项目的项目选择编程语言。想象一下在 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

TB2Z_nWsf5TBuNjSspmXXaDRVXa_!!2768491700.jpg_640x640q80_.webp

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK