当前位置: 首页 > 科技观察

Swift语言的设计错误

时间:2023-03-14 18:07:58 科技观察

在《编程的智慧》一文中,我对Swift语言的选项类型设计大加赞赏,但这并不代表Swift的设计绝对没有问题。事实上,当Swift1.0刚出来的时候,我就发现它的数组可变性设计存在严重的错误。Swift2.0修复了这个问题,但是他们的修复还是错误的。这个错误一直持续到今天。Swift1.0试图利用var和let的区别来指定数组成员的可变性,但实际上var和let只能指定数组引用的可变性,而不能指定数组成员的可变性。例如,Swift1.0试图实现这样的语义:varshoppingList=["Eggs","Milk"]shoppingList[0]="Salad"//你可以给数组成员赋值letshoppingList=["Eggs","牛奶"]shoppingList[0]="沙拉"//不能给数组成员赋值,报错,但这是错误的。为了理解这个问题,首先你应该分清楚数组引用和数组成员的区别。在这个例子中,shoppingList是一个数组引用,shoppingList[x]的形式是访问一个数组成员。因为var和let本来就是用来指定shoppingList的引用是否可变的,即判断shoppingList是否可以指向另一个数组。它不能用于指定数组成员的可变性。正确的用法应该是这样的:varshoppingList=["Eggs","Milk"]shoppingList=["Salad","Noodles"]//你可以给数组引用赋值letshoppingList=["Eggs","Milk"]shoppingList=["Salad","Noodles"]//不能给数组引用赋值,报错varandletcanonlyhaveanpurpose.一旦它们被用于指定引用的可变性,它们就不能再用于指定数组成员的可变性。如果你理解引用是放在栈(stack)上,而Swift1.0的数组是放在堆(heap)上,你就会明白var和let只能指定栈上数据的可变性。堆上的数据可变性必须以另一种方式指定。其实很多语言都已经看清了这个问题。C++程序员都知道intconst*和int*const的区别。ObjectiveC程序员知道NSArray和NSMutableArray之间的区别。我不知道为什么Swift的设计者看不到这个问题。Swift2.0修复了这个问题,但是它的修复方法是错误的:它把数组从引用类型变成了所谓的值类型,也就是说,把数组放在栈上而不是放在堆上。这似乎解决了上面的问题,因为数组既然是栈数据,shoppinList不再是引用,而是代表数组的数据本身,所以确实可以用var和let来判断是否可变。这似乎是一个可行的解决方案,但它没有达到目的并产生了另一个问题。使用数组作为值类型使得必须复制传递给数组的每个赋值或参数。你不能让两个变量指向同一个数组,这意味着数组不能再共享。这违反了程序员对数组等大型结构的“心智模型”。他们不能再清晰方便地思考数组,容易出错。而且复制数组需要花费大量的时间,对效率影响很大。没有其他现代语言(Java、C#等)将数组作为值类型。其实如果看透它的本质,就会发现值类型的存在其实是没有意义的。无数新语言,Swift、Rust等都尝试用值类型之类的方式来解决内存管理效率问题,但带来的好处微乎其微,给程序员带来的麻烦和困扰也是有目共睹的。从Swift的设计中犯下如此低级的错误,你或许就能看出编译器专家不等于编程语言专家。许多编程语言专家一看到Swift最初的数组设计,就知道这是错误的。为什么Swift的设计者直到1.0发布才发现,到2.0修复了还是不对?我猜这是因为Apple没有聘请合格的编程语言专家来设计Swift。Swift的首席设计师是LLVM的设计师ChrisLatner。ChrisLatner是一个不错的编译器专家,但他只能算是一个业余的编程语言设计者。编译器和编程语言真的是两个截然不同的领域。不要盲目认为好的编译器作者就能设计出好的编程语言。Swift语言团队犯了不该犯的错误,是因为他们没有招到深入本质的设计师。第一次应该做对,但需要多次返工。以至于每次发布新版本,都会有很多“breakingchanges”,导致之前版本的Swift代码不能再用了。这种情况并不是Swift语言的世界末日,但我希望Apple能尽快招募真正的语言设计专家并听取他们的建议。