真相总会不期而遇。它们总是不经意间降临,譬如当我读到这条微博的时候:
这是关于Facebook的Flow的一个很不错的讨论 – http://t.co/5KTKakDB0w
— David J. Pearce (@whileydave) November 23, 2014
David是
Whiley编程语言](http://whiley.org/)的作者,这门语言内建了许多静态类型检查的特性,它比较小众,但粉丝还不少。它的一个很有意的特性就是流敏感(flow sensitive)类型(有时也被称为流类型),当它与联合(union)类型配合使用的时候会比较有用。下面是从它的[使用向导中摘录的一个例子:
function indexOf(string str, char c) => null|int:
function split(string str, char c) => [string]:
var idx = indexOf(str,c)
// idx has type null|int
if idx is int:
// idx now has type int
string below = str[0..idx]
string above = str[idx..]
return [below,above]
else:
// idx now has type null
return [str] // no occurrence
记住了,像Ceylon这样的语言也支持流敏感类型,甚至是Java在也在一定程度上也是支持的,因为Java也有联合类型!
try {
...
}
catch (SQLException | IOException e) {
if (e instanceof SQLException)
doSomething((SQLException) e);
else
doSomethingElse((IOException) e);
}
Java的流敏感类型是显式且拖沓的。我们当然希望编译器能推导出所有的类型。像下面这么写的话也会进行类型检查并且能够通过编译就好了:
try {
...
}
catch (SQLException | IOException e) {
if (e instanceof SQLException)
// e is guaranteed to be of type SQLException
doSomething(e);
else
// e is guaranteed to be of type IOException
doSomethingElse(e);
}
流类型或者说流敏感类型指的是编译器可以从当前程序的控制流中推导出唯一可能的类型。它是在像
Ceylon这样的现代语言中才出现一个相对较新的概念,它使得静态类型变得异常强大,尤其是当语言本身能支持通过var或者val关键字来进行复杂的类型推导的时候。
配备了Flow之后的静态类型的JavaScript
我们回到David的那条微博并看一下这篇文章对Flow是如何评价的:
[http://sitr.us/2014/11/21/flow-is-the-javascript-type-checker-i-have-been-waiting-for.html
](http://sitr.us/2014/11/21/flow-is-the-javascript-type-checker-i-have-been-waiting-for.html)
[i]
由于length的取值可能为空,因此Flow就不得不在函数体内判断它是否为空值。下面是进行了类型检查的版本:
[i]
function length(x) {
if (x) {
return x.length;
} else {
return 0;
}
}
var total = length('Hello') + length(null);
Flow能够推导出在if体内x不能为空。
这相当巧妙。
微软的TypeScript中也有一个类似的新特性](https://github.com/Microsoft/TypeScript/issues/805)。但Flow与TypeScript不同(至少它是这么声称的)。从[官方的Flow介绍中可以看到Facebook Flow的本质所在:
Flow的类型检查是可选的——你不再需要在代码中到处进行类型检查了。然而,Flow的底层设计是基于这么一个假设的,即大多数JavaScript的代码其实都是静态类型的;尽管很多时候代码中并没有出现明确的类型,但在开发人员的脑海中它是实际存在的。Flow会尽可能地自动推导出这些类型,这意味着你无需修改代码便能找出里面的类型错误。换句话说,一些严重依赖于反射的JavaScript代码,尤其是框架的代码,通常很难进行静态检查。对于这种骨子里就是动态类型的代码,类型检查就变得不太准确了,因此Flow提供了一种简单的方式来显式地将这些代码置为是可信的,并忽略它们。这个设计在Facebook海量的JavaScript代码库中得到了验证:许多代码都默认归到了静态类型的分类,开发人员无需显式标注这些代码的类型便能找出其中类型错误的问题。
个中三昧
绝大多数JavaScript代码都是隐式的静态类型的
再进一步
JavaScript代码都是隐式的静态类型的
没错!
类型系统深受程序员的喜爱。他们喜欢正式地声明数据的类型,使得这些数据处于一个比较窄的约束下,这样才能确保程序的正确性。这正是静态类型的本质所在:一个设计良好的数据类型更不容易出错。
人们也喜欢将自己的数据结构以一种规范的形式存储到数据库中,这就是为什么SQL如此大行其道的原因,而无schema的数据库的市场份额始终上不去。这其实本质上都是一样的。在无schema的数据库中,其实你的脑子里还是存在一个schema,只是没有进行类型检查而已,并增加了确保程序正确性的负担。
还有一点需要注意的:一些NoSQL的厂商拼了命地在发表
这些荒谬不堪的言论,告诉你,其实你根本就不需要schema,其实只是为了给自己的产品找定位,不过这种营销的伎俩很容易识破。无schema与动态类型的真正需求其实,都相当的少。换言之,你上一次在Java程序中通过反射来调用方法是什么时候的事了?很少用到吧。
不过有一样东西是以前静态类型语言所不具备而动态类型语言能做到的:避免代码冗长。这是因为虽然程序员钟爱类型系统以及类型检查,但他们并不喜欢去敲这些代码。
代码冗长才是问题所在,而非静态类型
来看一下Java进化的历史吧:
Java 4
List list = new ArrayList();
list.add("abc");
list.add("xyz");
// 为什么我需要这个Iterator呢?
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
// 好吧,我清楚我这里就是String类型,为啥还要类型转换?
String value = (String) iterator.next();
// [...]
}
Java 5
// 我居然得声明两次泛型!
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("xyz");
// 比之前好多了,不过还是得声明是String类型
for (String value : list) {
// [...]
}
Java 7
// 进步了点
List<String> list = new ArrayList<>();
list.add("abc");
list.add("xyz");
for (String value : list) {
// [...]
}
// [...]
}
Java 8
// 终于进化成了这样,虽然姗姗来迟了
Stream.of("abc", "xyz").forEach(value -> {
// [...]
});
顺便提一下,当然了,上述这个功能其实用Arrays.asList()就能完成了。
Java 8还远谈不上完美,但至少是日臻完美了。现在在lambda参数列表中不用声明类型的原因是编译器替我们完成了推导,这点是相当重要的。
看一下Java 8之前类似于这个lambda的话要怎么写(假设那会儿已经有Stream了):
// Yes, it's a Consumer, fine. And yes it takes Strings
Stream.of("abc", "xyz").forEach(new Consumer<String>(){
// And yes, the method is called accept (who cares)
// And yes, it takes Strings (I already say so!?)
@Override
public void accept(String value) {
// [...]
}
});
现在我们拿Java 8跟JavaScript的版本作一个比较:
["abc", "xyz"].forEach(function(value) {
// [...]
});
在简洁性方面它几乎已经达到和函数式,动态类型的JavaScript一样的水准了,唯一的不同之处就在于,我们(以及编译器)知道这个value的类型是String。还有就是我们知道forEach方法的确存在。我们还知道的是forEach方法接受一个带单个参数的函数。
最后貌似可以得出如下的结论:
像JavaScript与PHP这样的动态类型语言之所以流行的原因是因为它们"能跑起来"。你不需要学习经典静态类型语言里面所有复杂的语法(想想Ada和PL/SQL吧!)。你马上就可以开始写代码了。程序员知道哪些变量包含字符串,因此没有必要写下来。这没错,的确是没有必要什么都写出来。
看一下Scala(或者C#,Ceylon,以及几乎任意的现代编程语言):
val value = "abc"
它除了能是字符串,还能是什么吗?
val list = List("abc", "xyz")
这个除了List[Stirng],还能是别的类型吗?
不过请注意了,如果你需要显式声明变量类型的话,也是可以的——总会有那么些个边缘的用例:
val list : List[url="abc", "xyz"]String] = List[String[/url]
不过绝大部分的语法都是“可选”的了,它们能由编译器来完成推导。
动态类型语言已死
讲的所有这些的结论就是,一旦语法的冗长及阻碍从静态类型语言中刨掉之后,那么使用动态类型语言就完全没有任何优势了。编译器已经相当快了,部署也很快速。只要使用了合适的工具,静态类型检查所带来的好处是巨大的。(不相信?
请读下这篇文章)。
举个例子,SQL也是一门静态类型语言,它的使用障碍主要是语法造成的。没错,很多人都认为它是一门动态类型语言,因为他们是通过JDBC来访问SQL的,比方通过一些无类型的SQL语句的字符串拼接而成。如果你写过PL/SQL,Transact-SQL或者
jOOQ写过嵌入式SQL的话,你绝对不会认为SQL是动态类型的,你马上就会感谢PL/SQL,Transact-SQL,以及你的Java编译器给你的SQL语句所做的类型检查了。
那么,让我们摒弃这个由我们一手创造出来的东西吧,因为我们实在是太懒了,不想在代码里声明这些类型。让敲代码变得更愉快吧!
如果你是Java语言的专家组成员,并碰巧看到了这篇文章,请你一定要把var和val关键字,以及流敏感类型添加到Java语言中。我保证一定会爱死你的!
原创文章转载请注明出处:
http://it.deepinmind.com
英文原文链接
分享到:
相关推荐
Simple语言可能需要一些运行时库来提供动态内存分配、异常处理等功能。这部分代码不直接由编译器生成,但编译器需要知道如何与之交互。 8. **学习资源** 对于想要深入学习编译器设计的学生,可以从这个项目中了解...
- **动态语言解释器**:如Python、JavaScript等语言的解释器,在程序运行过程中逐行解释执行。 - **混合模型**:结合编译和解释的特点,如Java虚拟机(JVM)先将Java源代码编译成字节码,然后由JVM解释执行字节码。 - ...
5. **监控**:安装后要定期检查网站日志,确认301重定向工作正常,没有错误的重定向或者死循环。 6. **搜索引擎提交**:完成301重定向后,建议通过Google Search Console或Bing Webmaster Tools等工具,通知搜索...
【动态优先权的进程调度算法】是...这个实验通过模拟动态优先权调度,有助于学生理解和掌握进程调度的原理,以及如何用编程语言实现这些概念。在VC++环境下,可以使用类似的方法编写并运行代码,以观察和分析调度行为。
2. 感染者(Infected, I):已感染疾病并具有传染性的个体。 3. 康复者(Recovered/Removed, R):康复并获得免疫力或者因疾病去世的个体。 该模型通过一组微分方程描述这三个状态群体随时间的变化,主要参数包括:...
slice的长度是可变的,表示slice中已使用的元素数量,而容量则是slice可以容纳的最大元素数量,不包括超出长度的元素。因此,直接对slice进行切片操作时,如果不注意长度和容量的关系,可能会导致意外的结果。 其次...
在**发布模式**下,编译器为了提高性能,可能会进行函数内联、死代码删除、循环展开等一系列优化,这可能会影响到动态生成代码的正确性,特别是当依赖于函数地址时。 例如,如果动态生成的代码依赖于某个函数的地址...
1. 源代码文件:包含迷宫生成、动态变化和音乐播放的实现,可能使用了某种编程语言,如C++、Python或Java。 2. 资源文件:包括迷宫的图像、音效和背景音乐等,这些文件可能以.png、.wav或.mp3格式存储。 3. 配置文件...
“nabo:Nabo(纳博)-已死的简单博客引擎”表明这个项目是一个名为“Nabo”的博客引擎,基于Elixir编程语言,但已经被标记为“已死”,意味着它可能不再维护或更新。 **描述解析:** 描述与标题相同,进一步确认...
- 原型(Prototype):通过复制已有对象来创建新对象。 - 单例(Singleton):保证一个类只有一个实例,并提供全局访问点。 2. 结构型模式: - 适配器(Adapter):使两个接口不兼容的类能够协同工作。 - 桥接...
———————作者Boyle Gu中文自述文件[中文]概述现在说到Web开发领域,提到Java就觉得它非常臃肿,陈旧,而且开发效率还不如其他动态语言,甚至之前有人高呼“Java已死”。但事实真是如此吗?其实,如果你长期...
"CodeDOM Go Kit:CodeDOM已死,CodeDOM万岁"这个标题可能暗示了一个关于CodeDOM的新工具或库,它可能改进了CodeDOM的一些不足,或者提供了更高效的使用方式。 CodeDOM最初设计用于支持多种编程语言,如VB.NET和C#...
scheb /墓碑 ... 该库为您提供了一个工具箱,用于在代码中放置,跟踪和评估逻辑删除。... 一段时间后,日志会告诉您哪些墓碑已死,哪些墓碑未死(所谓的“吸血鬼”)。 安装 该库由多个组件组成,需要独立安装和配
由于这个窗口在程序启动前就已经创建,因此尝试动态hook来改变类名可能并不奏效。 一种简单而有效的方法是,通过修改程序二进制文件,改变注册类名和创建窗口这两步操作的指令。这里可以利用“jmp”指令来跳过这...
端口号是一个16位的数字,范围从0到65535,其中0-1023是已分配给特定服务的知名端口,而1024-49151是注册端口,剩余的是动态或私有端口。 当我们遇到网络连接问题时,比如某个服务无法启动或者响应慢,通过端口查询...
- 运用丰富的语言描写和人物动态描写,生动展现了一场伟大的农民起义。 通过这些知识点的复习,我们可以深入理解《陈涉世家》的历史背景、人物性格以及其作为农民起义代表的意义。这篇复习文档有助于学生全面掌握...
局限性由于PHP是一种非常动态的编程语言,因此由phpdcd执行的静态分析无法识别使用以下语言功能之一执行的函数或方法调用: 反射API call_user_func()和call_user_func_array() 带有变量类名的new运算符的用法静态...
例如,“mortal(socrates).” 是一个查询,Prolog将尝试根据已有的事实和规则来判断苏格拉底是否为会死亡的。 - **Prolog程序的特点**: - **无特定运行顺序**:与大多数编程语言不同,Prolog程序的运行顺序不是...
建立克里斯托弗·亚历山大(Christopher Alexander)定义的模式语言和生成序列。 除非另有说明,否则所有作品均根据CC-BY-SA许可。 什么是模式? 模式是经过验证的解决方案,可以解决在某些情况下发生的重复出现的...
在IT领域,迷宫求解是一个经典的问题,而在这个场景中,我们看到的是一个使用Java编程语言实现的迷宫求解算法,它基于栈数据结构。这个名为"Migong.rar"的压缩包包含了一个名为"MiGong"的程序,以及一个可能是链接或...