Mojo 仍然是一种新语言,并且正在迅速发展。我们从其他语言中学到了很多东西,但 Mojo 提出了自己的一套权衡,表明了独特的设计点。
Mojo 开发过程中做出的早期决定之一是采用 Swift 使用的设计点let
。本白皮书认为,我们应该通过抛弃并保留 (以及 中的隐式 Python 风格变量声明)var
来切换到更简单的模型。
请注意,不变性和值语义仍然是 Mojo 设计的重要部分,这只是删除“命名的不可变变量”。不可变引用尤其重要,并将成为未来“生命周期”功能的一部分。
当前 Mojo 0.6 设计
Mojo 最初遵循 Swift 的先例,允许编码人员在let
andvar
关键字之间进行选择,以确定某些内容是否可以在本地修改。也就是说,Mojo 0.6 及更早版本的设计有许多特别令人惊讶和不幸的方面:
不可变变量的概念对于 Python 程序员来说是全新的,之前使用 Swift 的经验表明,这最终成为 Swift 程序员必须学习的第一个概念。这是不幸的,因为命名不可变变量不是核心编程概念,也不是实现 Mojo 目标所需的东西。
这个命名let
引起了早期的热议和争论。其他编程语言具有广泛的设计点(例如,const
C/C++ 和 Javascript),并且所有这些事物的命名也存在差异: let
、val
、const
等。
Mojo 还有一个编译时值 ( alias
) 的概念,这意味着存在三个概念:alias
、let
和var
。 Javascript的大多数用途(例如)const
比alias
.let
Swift 和 Rust 都鼓励不可变的值 - Swift(以及当前的 Mojo)警告不需要的可变性,Rust 使可变性更加详细(let mut
),并且一些人建议 Mojo让可变性更加详细。这对 Python 的许多设计中心来说是非常困难的,Python 的设计中心甚至根本没有这个概念:将其设为默认值会很奇怪,但如果我们不这样做,那为什么还要麻烦拥有它呢?
Swift/Rust 设计没有任何性能优势,而且我个人还没有看到任何数据表明存在任何安全性或可读性优势。如果有的话,那就是当let x = foo()
你知道x
永远不会被重新分配时,这是一个争论,但这里的任何好处都很小。
不变性仅适用于本地值,对于引用语义类型(例如Pointer
今天的 Mojo 中的类型,以及 明天的 Mojo 中的所有类),这是非常令人困惑的。我们经常被问到:“当我明确改变了‘var
指针’let
所指向的内容时,为什么我会收到一条警告,要求我更改它?”
Mojo 目前不允许let
's 作为结构字段(仅var
's),这是不一致的。 Swift 对于如何初始化结构体字段有一套非常复杂的规则,Mojo 最好不要实现这些规则。也没有一个很好的方法来定义默认字段值,例如:
struct Thing:
# This is not actually supported right now, but imagine it were.
let field = 42
fn __init__(inout self):
self.field = 17 # shouldn't be able to overwrite field?
- Mojo 有所有权的概念,并且最终将有生命周期和安全引用的概念(包括可变和不可变引用),这将不同于(但可以组合)
let
vsvar
区别。不幸的是,存在不同形式的不可变性,我们确实需要不可变的借用和不可变的引用。
作为 Swift 的主要设计者之一,我主观地说,它有几个相当迂腐的语言功能,旨在提高安全性(例如,要求所有可能抛出的值都用关键字标记try
),并且许多决定都是在没有考虑的情况下尽早做出的。有很多数据来支持他们。我相信我们应该努力让 Mojo 易于学习,并尽可能消除不必要的概念。
建议:消除 ' let
' 声明
这里的建议很简单:让我们完全消除不可变命名值的概念。这不会消除 Mojo 中的不变性这一概念,而是会将其推入借用参数和不可变引用的世界。这将有许多优点:
这直接简化了概念性的Mojo语言模型:
- 这消除了 Python 程序必须学习的一个不熟悉的概念。
- 这消除了
let
与alias
直接之间的混淆。
- 这消除了关键词“bikeshedding”的丰富来源。
- 这消除了工作簿中的混乱,因为即使声明了顶层值,它们也是可变的
let
。
这也将消除编译器中的大量复杂性:
- 当前错误消息必须确保表述
let
正确var
。
- IR 表示需要跟踪这一点以进行后续语义检查。
- 这消除了实现缺少的功能来支持
let
的需要。
var
这消除了检测警告更改为 的未突变 s 的复杂性let
。
- 由于 ASAP 销毁,CheckLifetimes 具有额外的复杂性来拒绝诸如“
let x: Int; x = 1; use(x); x = 2; use(x)
”之类的代码,即使第一个“ x=1
”的原始生命周期自然结束并且“ x
”在分配之前未初始化。这一直是一种设计味道,而且 行不通。
据我们所知,该提议根本不会影响运行时性能。
那var呢?
如果这个提议被接受,我认为我们应该保持var
原样。与传统的 Python 行为不同,var
引入了显式声明和 词法作用域的值:我们需要一些引入器并且确实需要作用域声明。
这个名称var
也较少引起争议,因为它清楚地代表“变量”,而不是用来let
代表“命名常量”。如果需要重命名,var
它将与此进行正交讨论,并且应分开讨论。
顺利推行此提案
如果我们认为这个提案是个好主意,那么我认为我们应该分阶段进行,以使采用更加顺利,减少干扰。推出它看起来像这样:
- 与 Mojo 社区达成共识,以获得反馈和其他观点。
- 进行工程工作以验证没有性能影响等,并消除 IR 表示和行为
let
。在此阶段,我们将继续解析它们以实现兼容性:将它们解析为与 a 相同的 IR var
,但发出警告“let 已被弃用,将在下一个版本中删除”,并带有将 a 重命名为的修复let
提示var
。
- 在大约 1 个月后的版本中,将警告更改为错误。
- 在大约 1 个月后的版本中,完全删除该关键字以及错误消息。
考虑的替代方案
我们可以随时保留它并稍后重新评估。也就是说,我认为这里不会有任何改变 - Mojo 用户社区(无论是模块化的外部还是内部)已经遇到过几次这种情况,而且这种情况还会不断出现。