【转】Swift 语言的设计错误
Swift 语言的设计错误
在『编程的智慧』一文中,我分析和肯定了 Swift 语言的 optional type 设计,但这并不等于 Swift 语言的整体设计是完美没有问题的。其实 Swift 1.0 刚出来的时候,我就发现它的 array 可变性设计存在严重的错误。Swift 2.0 修正了这个问题,然而他们的修正方法却没有击中要害,所以导致了其它的问题。这个错误一直延续到今天。
Swift 1.0 试图利用 var 和 let 的区别来指定 array 成员的可变性,然而其实 var 和 let 只能指定 array reference 的可变性,而不能指定 array 成员的可变性。举个例子,Swift 1.0 试图实现这样的语义:
var shoppingList = ["Eggs", "Milk"]
// 可以对 array 成员赋值
shoppingList[0] = "Salad"
let shoppingList = ["Eggs", "Milk"]
// 不能对 array 成员赋值,报错
shoppingList[0] = "Salad"
这是错误的。在 Swift 1.0 里面,array 像其它的 object 一样,是一种“reference type”。为了理解这个问题,你应该清晰地区分 array reference 和 array 成员的区别。在这个例子里,shoppingList
是一个 array reference,而 shoppingList[0]
是访问一个 array 成员,这两者有着非常大的不同。
var 和 let 本来是用于指定 shoppingList
这个 reference 是否可变,也就是决定 shoppingList
是否可以指向另一个 array 对象。正确的用法应该是这样:
var shoppingList = ["Eggs", "Milk"]
// 可以对 array reference 赋值
shoppingList = ["Salad", "Noodles"]
// 可以对 array 成员赋值
shoppingList[0] = "Salad"
let shoppingList = ["Eggs", "Milk"]
// 不能对 array reference 赋值,报错
shoppingList = ["Salad", "Noodles"]
// let 不能限制对 array 成员赋值,不报错
shoppingList[0] = "Salad"
也就是说你可以用 var 和 let 来限制 shoppingList
这个 reference 的可变性,而不能用来限制 shoppingList[0]
这样的成员访问的可变性。
var 和 let 一旦被用于指定 array reference 的可变性,就不再能用于指定 array 成员的可变性。实际上 var 和 let 用于局部变量定义的时候,只能指定栈上数据的可变性。如果你理解 reference 是放在栈(stack)上的,而 Swift 1.0 的 array 是放在堆(heap)上的,就会明白array 成员(一种堆数据)可变性,必须用另外的方式来指定,而不能用 var 和 let。
很多古老的语言都已经看清楚了这个问题,它们明确的用两种不同的方式来指定栈和堆数据的可变性。C++ 程序员都知道 int const *
和 int * const
的区别。Objective C 程序员都知道 NSArray
和 NSMutableArray
的区别。我不知道为什么 Swift 的设计者看不到这个问题,试图用同样的关键字(var 和 let)来指定栈和堆两种不同位置数据的可变性。实际上,不可变数组和可变数组,应该使用两种不同的类型来表示,就像 Objective C 的 NSArray
和 NSMutableArray
那样,而不应该使用 var 和 let 来区分。
Swift 2.0 修正了这个问题,然而可惜的是,它的修正方式是错误的。Swift 2.0 做出了一个离谱的改动,它把 array 从 reference type 变成了所谓 value type,也就是说把整个 array 放在栈上,而不是堆上。这貌似解决了以上的问题,由于 array 成了 value type,那么 shoppingList
就不是 reference,而代表整个 array 本身。所以在 array 是 value type 的情况下,你确实可以用 var 和 let 来决定它的成员是否可变。
let shoppingList = ["Eggs", "Milk"]
// 不能对 array 成员赋值,因为 shoppingList 是 value type
// 它表示整个 array 而不是一个指针
// 这个 array 的任何一部分都不可变
shoppingList[0] = "Salad"
这看似一个可行的解决方案,然而它却没有击中要害。这是一种削足适履的做法,它带来了另外的问题。把 array 作为 value type,使得每一次对 array 变量的赋值或者参数传递,都必须进行拷贝。你没法让两个变量指向同一个 array,也就是说 array 不再能被共享。比如:
var a = [1, 2, 3]
// a 的内容被拷贝给 b
// a 和 b 是两个不同的 array,有相同的内容
var b = a
这违反了程序员对于数组这种大型结构的心理模型,他们不再能清晰方便的对 array 进行思考。由于 array 会被不经意的自动拷贝,很容易犯错误。数组拷贝需要大量时间,就算接收者不修改它也必须拷贝,所以效率上有很大影响。不能共享同一个 array,在里面读写数据,是一个很大的功能缺失。由于这个原因,没有任何其它现代语言(Java,C#,……)把 array 作为 value type。
如果你看透了 value type 的实质,就会发现这整个概念的存在,在具有垃圾回收(GC)的现代语言里,几乎是没有意义的。有些新语言比如 Swift 和 Rust,试图利用 value type 来解决内存管理的效率问题,然而它带来的性能提升其实是微乎其微的,给程序员带来的麻烦和困扰却是有目共睹的。完全使用 reference type 的语言(比如 Java,Scheme,Python),程序员不需要思考 value type 和 reference type 的区别,大大简化和加速了编程的思维过程。Java 不但有非常高效的 GC,还可以利用 escape analysis 自动把某些堆数据放在栈上,程序员不需要思考就可以达到 value type 带来的那么一点点性能提升。相比之下,Swift,Rust 和 C# 的 value type 制造的更多是麻烦,而没有带来实在的性能优势。
Swift 1.0 犯下这种我一眼就看出来的低级错误,你也许从中发现了一个道理:编译器专家并不等于程序语言专家。很多经验老到的程序语言专家一看到 Swift 最初的 array 设计,就知道那是错的。只要团队里有一个语言专家指出了这个问题,就不需要这样反复的修改折腾。为什么 Swift 直到 1.0 发布都没有发现这个问题,到了 2.0 修正却仍然是错的?我猜这是因为 Apple 并没有聘请到合格的程序语言专家来进行 Swift 的设计,或者有合格的人,然而他们的建议却没有被领导采纳。Swift 的首席设计师是 Chris Lattner,也就是 LLVM 的设计者。他是不错的编译器专家,然而在程序语言设计方面,恐怕只能算业余水平。编译器和程序语言,真的是两个非常不同的领域。Apple 的领导们以为好的编译器作者就能设计出好的程序语言,以至于让 Chris Lattner 做了总设计师。
Swift 团队不像 Go 语言团队完全是一知半解的外行,他们在语言方面确实有一定的基础,所以 Swift 在大体上不会有特别严重的问题。然而可以看出来这些人功力还不够深厚,略带年轻人的自负,浮躁,盲目的创新和借鉴精神。有些设计并不是出自自己深入的见解,而只是“借鉴”其它语言的做法,所以可能犯下经验丰富的语言专家根本不会犯的错误。第一次就应该做对的事情,却需要经过多次返工。以至于每出一个新的版本,就出现一些“不兼容改动”,导致老版本语言写出来的代码不再能用。这个趋势在 Swift 3.0 还要继续。由于 Apple 的统治地位,这种情况对于 Swift 语言也许不是世界末日,然而它确实犯了语言设计的大忌。一个好的语言可以缺少一些特性,但它绝不应该加入错误的设计,导致日后出现不兼容的改变。我希望 Apple 能够早日招募到资深一些的语言设计专家,虚心采纳他们的建议。BTW,如果 Apple 支付足够多的费用,我倒可以考虑兼职做他们的语言设计顾问 ;-)
Java 有 value type 吗?
有人看了以上的内容,问我:“你说 Java 只有 reference type,但是根据 Java 的官方文档,Java 也有 value type 和 reference type 的区别的。” 由于这个问题相当的有趣,我另外写了一篇文章来回答这个问题。
【转】Swift 语言的设计错误的更多相关文章
- 苹果新的编程语言 Swift 语言进阶(一)--综述
Swift 是苹果开发和提供的供开发IOS 和OS X应用的一门新的语言.Swift语言基于C 和Objective-C语言,除了提供C 和Objective-C语言具有的所有语法功能外,为了编程方便 ...
- 苹果新的编程语言 Swift 语言进阶(二)--基本数据类型
一 . 常量和变量 Swift语言 对常量和变量的声明进行了明确的区分 Swift语言的常量类型比C 语言的constants类型更加强大,语义更加明确. 常量和变量的区别是常量在设置或初始化后 ...
- 【转】从Go、Swift语言出发
Google于2009年第一次提出了Go的构思,Facebook在去年春天引入了Hack,随后不久Apple也发布了其Swift语言. 在战争中,胜利者写历史书:在科技中,赢的公司都在写编程语言.互联 ...
- Swift语言Auto Layout入门教程:上篇
原文:Beginning Auto Layout Tutorial in Swift: Part 1/2,译者:@TurtleFromMars 开始用自动布局约束的方式思考吧! 更新记录:该教程由Br ...
- Swift语言快速入门
Swift语言快速入门(首部同步新版官方API文档和语法的Swift图书,确保代码可编译,作者专家在线答疑,图书勘误实时跟进) 极客学院 编著 ISBN 978-7-121-24328-8 201 ...
- IOS系列swift语言之课时二
今天我们要讲的就是函数[对于函数,在最后面还有几道题,喜欢的博友可以看了自己做一下,和我交流一下] 当然这与我们的c语言还是有一定的共同之处的,对于有一些c语言或者是java基础的童鞋,我觉得是很容易 ...
- IOS入门之Swift语言(一)
经过不断的努力,小哥也买了台苹果设备,终于可以开始我的IOS之旅了,说来确实令人苦恼,为了学习IOS我这着贫农阶级,省了几个月的零花钱,外加向亲朋好友求救,最终痛下心扉,卖了台MAC pro128G版 ...
- IOS系列swift语言之课时八
这节课需要讲的就是可选链,内存管理,引用计数,unowned解决 //: Playground - noun: a place where people can play import UIKit / ...
- Swift - 语言指南,来自github学习
@SwiftLanguage 更新于 2016-6-6,更新内容详见 Issue 55.往期更新回顾详见<收录周报> 这份指南汇集了 Swift 语言主流学习资源,并以开发者的视角整理编排 ...
随机推荐
- SpringMVC验证框架Validation特殊用法
基本用法不说了,网上例子很多,这里主要介绍下比较特殊情况下使用的方法. 1. 分组 有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的id来说,保存 ...
- Unity3D 导入Xcode 工程后。编译很慢
Unity3D 导入Xcode 工程后.编译很慢 选择Targets-->Build options -->debug information format 然后选择DWARF 这样再次 ...
- linux CPU占用率高(转)
来自:http://www.cnitblog.com/houcy/archive/2012/11/28/86801.html 1.用top命令查看哪个进程占用CPU高 gateway网关进程14094 ...
- Spark分布式集群的搭建和运行
集群共三台CentOS虚拟机,一个Matser,主机名为master:三个Worker,主机名分别为master.slave03.slave04.前提是Hadoop和Zookeeper已经安装并且开始 ...
- MongoDB numa系列问题三:overcommit_memory和zone_reclaim_mode
内核参数overcommit_memory : 它是 内存分配策略 可选值:0.1.2.0:表示内核将检查是否有足够的可用内存供应用进程使用:如果有足够的可用内存,内存申请允许:否则,内存申请失败,并 ...
- 使用SpringBoot创建Web项目
1.pom.xml 引入 <parent> <groupId>org.springframework.boot</groupId> <artifactId&g ...
- SpringMVC学习笔记五:使用converter进行参数数据转换
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6832898.html 一:SpringMVC数据绑定机制 1:request到达SpringMVC框架时,框 ...
- 利用WatchService监听文件变化
在实现配置中心的多种方案中,有基于JDK7+的WatchService方法,其在单机应用中还是挺有实践的意义的. 代码如下: package com.longge.mytest; import jav ...
- 在大负载中使用LoadRunner进行负载测试()
在大负载中使用LoadRunner进行负载测试,需要配置一些环境来满足大负载下各种资源的充足: 1.为了避免出现“No Buffer Space Available”的错误,需要进行如下配置: 1)修 ...
- Paint的基本使用
代码地址如下:http://www.demodashi.com/demo/14712.html 前言 在讲述自定义控件的时候,我们讲到了自定义控件的基本步骤,那么在自定义控件中,我们第一个需要了解的就 ...