[翻译]Go编程语言,或者:为什么除了它,其他类C语言都是垃圾(1)

原文在这里:http://www.syntax-k.de/projekte/go-review。作者是 Jörg Walter,是个德国人。

根据 Rob Pike 在推上的说法,文章有一些错误,但看起来值得阅读。

“Why all C-like languages except one suck”: http://www.syntax-k.de/projekte/go-review Some errors in there but a positive response.

———————–翻译分割线———————–

Go编程语言,或者:为什么除了它,其他类C语言都是垃圾

2011-06-07

Jörg Walter 撰写

简介

这是关于 Robert Griesemer,Rob Pike 和 Ken Thompson 在 Google 从 2007 年开发的 Go 语言的综述。现在,Ian Taylor,Russ Cox 和 Andrew Gerrand 已经加入了核心团队。这是一个类 C 语言,有一些动态脚本语言的特性,并且提供了一些关于并行和面向对象的新奇的(至少在通用语言领域)特性。它的目的是成为系统开发语言,这也是为什么这篇综述选择同其他类 C 语言,而不是脚本语言进行比较。

在编写这个综述的时候我发现,与其对 Go 进行评估,不如说它有更多值得详细解说的功能。Go 是完全不同的,不能用标准的 OO 背景知识去判断它。因此,这个综述可能更像是一个关于 Go 的介绍。我是一个 Go 新手。编写这个也帮助我去了解 Go 是什么,并且如何做,但务必记得我的第一个 Go 应用只开发了一半。我用过许多语言进行编码,因此将会用 Go 同它们做一些比较。

Go 很年轻。它在今年才被标识为稳定版本。通过这个综述,我同样期望能够对 Go 未来的方向的讨论做一些贡献,使其成为一个了不起的语言。 这包括了指出仍然存在的一些缺陷。

关于 C 系语言的咒骂

我总是对新的编程语言感兴趣。通常,我使用方便的动态语言,例如 JavaScript、Perl 或者更新一些的 Python。大多数情况下,我更喜欢可读的、可维护的、高开发效率超过单一的性能测试。过早的优化通常都不值得。安全也是一个方面,而脚本语言将你从缓冲区溢出、字符串格式化缺陷和其他底层攻击的世界中解救出来(假设无法利用运行时环境)。

但是这些语言也有负面问题。它们不够灵活,而我的工作从 ARM 系统上的 8-bit MCU 和智能手机到标准的桌面应用。我尝试在其上使用C(++),但那使我感到很痛苦。没有便利的字符串运算符、依赖外部库的笨重的正则表达式、手工内容管理,当然还有持续了 40 年的安全梦魇。除此之外它能很好的工作,提供了几乎能实现的最快的内存效率。

在底层语言中,缺失了一些基础的东西。用于编写底层工具和操作系统的东西,都相当古老,并且被现代系统环境所遗忘。这就是我这里所期望展示的。仅涵盖了 C 血统语言,就像人们习惯的那样, 但是你可以很容易的增加 Pascal、Modula、Oberon、Smalltalk、Lisp 以及其他任何在计算机系统中做过核心的语言。

C

我喜欢 C。确实是这样。它的简单使得其相当优美。除非你去做一些愚蠢的事情,例如用 C 作为主要 API 来编写复杂的 GUI 开发包

一个的确非常好的事情是,你可以像用汇编那样进行控制,在特定场景中这确实是非常重要的。除了进行优化,绝对是。然后救是来自 C 的令人费解的语法的回击,例如不能对存放敏感数据的内存回收,或者在记录声明之间进行同步的障碍。这不可避免。如果有指针和运算,就需要语言中隐含的语义来保证优化不会变得更糟。

但是真正糟糕的是字符串、内存管理、数组等等……任何可能被利用的东西。并且难以使用。所以,C 可能是像其表现的那样轻量级的,它并不适合那些超过 10k 行代码的项目(译注:偏激了点,不是吗?)。

C++

C++ 改进了 C 的一些方面,但是带来了其他一些更糟糕的东西,尤其是无用的冗长的语法。你总是能找到那些束缚。不过对于标准应用开发它总是被优先选择的,一些不错的轻量的全功能 GUI 开发包很好的使用了这些优点。

但是当你尝试一些现代的动态语言功能(lambda 函数、map/reduce、类型推断……)时,通常都是这个路子“嘿,很酷!这个其实可以用 C++ 实现!你只需要这样做

dynamic_cast<MyData*>(funky_iterator<MyData &const*>(foo::iterator_type<MyData>(obj))

好了,就这样!”

别误导我,我钟爱模板,但是使用 STL 的 C++ 看起来就像一个标准的情况“如果你只有一把锤子”综合症。GCC 不得不实现特别的诊断和简化,最终发现其实长达 5 行的错误消息只是在使用 std::string 方法时,简单的常量化错误。同样糟糕的是,它们像地狱般的缓慢。那么等待 Boost 编译如何?好的主意,糟的现实。

Objective C

现在我觉得自己有点像异教徒。我不应该贬低任何来自 NeXT 的东西。但是,我忍不住——束缚感同样存在于 Objective C。它的冗长并不像 C++ 那样,但是括号语法就像进入来 C 的平行世界,这样 C 的全部语法都可用。

如果你对在 C++ 中编写模板转换不是那么印象深刻(这或许是个优点^^),并且仍然在使用手工方式管理内存。在 C++ 中至少可以用引用计数的方式释放内存(如果你曾经在数千错误的模板中找到语法正确的那个)。

Objective C 针对这个问题,跨越性的官方提供了可选的 GC。等等——这对于 C 系语言来说总是可选的。Boehm-GC 存在了多少年了?但是标准是采用内存池,在许多情况下很好的兼容性使得其能够工作,但是兼容性也就好成那样。

Java

你不会真以为在咒骂 C 系语言的时候我会忘记 Java,是吗?当前来说,Java 几乎总是解决方案。几乎得有点不真实。

有二进制夸平台能力,有着各种实现的、没有重大陷阱的语言细节的规格说明书,标准的 OO,垃圾回收,一些真正的动态特性,最终甚至是泛型——以及禁止消耗内存和拖慢启动时间。

其实没有明确的需求来解释为什么是这样。增强的 JIT 编译器(理论上)可以比其他任何预先进行优化的静态编译器做得更好。GCJ 编译器可处理机器码,如果你希望它这么做的话。VM 也有完全匹配的硬件实现。但是 JDK 使得基础臃肿,同时许多 Java 项目也有着拜占庭风格的复杂(译注:拜占庭风格总是绚丽且奢华的,但是似乎总有点华而不实,不是么?)。

现在,可以很舒适的编写现代的 Java 了。Web 服务提供了一些真正出色的抽象。直到你掀开帽子,然后发现一些小题大做的机制。每一层都用上年最流行的抽象层构建。你不能对向下兼容妥协,对吗?

看看你通常的 Web 应用的库目录。我不会对看到上百个 JAR 文件感到惊讶,对于用 PHP 实现的简单的数据库搜索或者购物网站,可以在 10k 行代码以内。或者你很有冒险精神,尝试自行编译。世界上最有乐趣的事情!设置一个 Linux 系统,没有任何 step-by-step 的介绍,这很容易。相信我,我都做过。在你开始之前,请务必确保你知道如何正向和反向拼写“dependency hell”(依赖的地狱)。

而所有这些都包含在繁琐的语法和古老的学校里的对象模型中。这基本上没有什么错误,但是有更好的办法。艺术品味是另一回事。看看 Perl6,尝试将所有现代语言设计放到一起,使其成为可用(真正有价值的“可用”)的语言。而其第一个功能生产可用的版本已经有十年以上了!Java 现在也快如此了,除了泛型以外。

C#

我差点忘了这个。其实我确实忘了这个,直到有反馈提醒了我。坦白说,我不了解 C#。作为语言,它看起来不错,C 和 C++ 的很好的发展。让我对其保持距离的是它非自由的出身。是的,有 Mono,但是我从不喜欢让我的工作建立在一个微软随时可以转为专利诉讼的慈善性质许可的语言上。我们都知道关于这个公司(好吧,实际上是任何大公司)实施它奴役的诡计。

我看不到编写不能夸平台的代码的亮点,因此尝试 Mono 对于我来说是种危险,我总是远离它。同样,当 Java VM 的强势和弱势被广为流传的时候,CLR 想必也已经获得了足够多的声誉。我可能会在某个时间写 C# 代码,但这天不会很快到来。

没有开放的生态系统的促进,以及特殊目的的实现,它不适合作为系统编程语言。仅仅适合企业开发。

哦,还有企业期望从语言的开发中赚取金钱,这不是一个健康的发展途径。商业兴趣将会强加一些对于语言不好的东西。改进的压力长期存在,否则就卖不出去。作为对比,来看看 Tex,有着 30 年以上历史的,并且作为软件可能达到的无 bug 的状态。无法真正对比两者,不过可以展示哪里是终点,而 C# 站在了错误的地方。

JavaScript

JavaScript 似乎不应该出现在这儿,因为它是一个完全动态的语言。它是最广泛使用的语言之一,然而,它也是 C 系的。并且,更重要的是,JS 引擎这段时间支持了相当高级的 JIT 编译器优化,因此性能同这里提到的其他语言相差无几。

那么,JS 有什么本质错误呢?不多。只是它不适宜用在小型系统上。它是很棒的应用嵌入式语言,也适合编写网络服务,但是设计使得它没有办法同外部世界交互。主应用在实现容器中定义了所有的交互 API,因此决定了它无法作为一个系统的主语言。

更进一步,JIT 和运行时的需求局限了它的嵌入用途。如果有一个 Palm Pre,并使用 JS 作为嵌入语言,这是相当方便的。当 500MHz/256MB 的系统是底线时,是可用的。可能最低的使用 JS(或者 ECMAScript) 作为系统主要语言的设备,是 Chumby,在 450MHz/64MB 上播放 Adobe Flash Lite 影片。那里并没有真正的通用语言。

心愿

亲爱的圣诞老人,所有的底层语言都很烂。圣诞节我想要一个有以下特点的编程语言(用已有语言作为例子),排名不分先后:

一般设计原则

1. 表达能力

我期望有一个语言可以有足够高的层次用来表达我的想法和算法,而不是在乏味的任务管理或 80% 都是模板代码上浪费时间和屏幕空间。更重要的体现表达能力的元素被单独列出。编写词法分析器是一个好的测试。如果最终的结果代码与分析的构造有许多相似之处,这就是一个正确的方向。(例如:Perl 6 的语法)

2. 简单

我期望有一个语言有着优雅的简单。只需要几个概念就可以表达所有的可能。正交性是这个情况的关键。简单也使得语言容易学习。在相同的目的中重用语法结构,并且让用户代码与这些结构对接,这样就可以在其上进行扩充。同样的,不要强迫用户使用复杂的结构。提供类、单元测试框架或者文档注释没有错,但是如果用户不想要的话,不要让它们进入视线。 理想情况,让它们“Do what I mean”。

3. 平等权利

如果内建的联合数组算法对于特定的数据集是低效的,我期望可以建立一个能像内建那个一样使用的实现来代替。语言不应该有特权。因此,操作符应当可以被重载。噢,在 Python 里做得那些魔法般的事……同样,内建数据类型应当可以作为对象语法在语言中使用。所有这些无须动态,多数都可以用静态的语法糖来实现。(例如:Perl 的 prototyped subs,Python 的操作符重载)

4. 元编程

这里有两个方面。一个是模板以及/或者泛型,这太有用了,以至于不能没有。虽然 C 有一些邪恶的 hack 来模拟这个:预处理程序。每个人都需要这个。更加高级的方面是编译时执行代码生成代码,像 lisp 的宏和 Perl 的代码 filter。额外说明一下,这允许创建新的语言结构或者特定领域的语言。

5. 高效的和可预测的

如果语言瞄准的是系统编程,它必须相当高效并且完全可以被预测。必须能够直观的预测确定的操作带来了哪些时间消耗和内存分配。核心的语言功能必须很好的优化。库提供了更高层次的结构使得编码更有效率,而执行却低效一些。对象系统无须昂贵的运行时支持(不论是时间还是空间),静态优化应当是可能的。

理想情况,用几千行代码和几百字节内存编写一个有用的控制程序是可能的。当然,效率和功能通常作用相反,需要取舍,所以语言应当提供这样的机会让我来决定。

6. 可用性

最终,语言必须可用。所有想法放到一边,最重要的还是它解决实际问题。用一点实用主义不会有伤害。语言应当与其他语言接近,这样就可以用之前思考的模式去思考它。如果严重的偏离常规,那它就必须是值得的。忠于原则,而避免惊异!

7. 模块化的库和代码仓库

我期望在成长过程中使用过的脚本语言内建的或部分标准库的那种精巧。一个包的公共代码仓库和适当的可移植的包管理则更好。通常,包包含网络协议、常用格式的解析、GUI、加密、通用数学算法、数据处理等等。(例如:Perl 5 CPAN)

特别的功能

8. 数据结构

我要我的哈希!一个没有同语言良好集成的联合数组数据类型的语言是一个笑话。显而易见,OO(或者其他便利的封装)也是必须的。布尔类型很好,但是更重要的是各种类型在布尔上下文中的明确的解释。额外的,在标准库中有多种高级数据结构,例如不同的树、列表、集合等等。如果已经有了跳跃链表,那就是上轨道了。

让字符串也是 OO 的,尤其是相似的运算符(如 len())作用于字符串应当像作用在数组或模拟数组的对象上一样。附加的,还要看是否所有原始数据类型支持与其他所有对象相同的 OO 语法。无须像 Ruby 那样。只要有语法糖即可。(例如:Python 和它的标准库)

9. 控制结构

听上去显而易见,不过允许一次从多个嵌套的循环中跳出的控制是恰当的。消除 goto,真的。上一次我用到它是……好吧,就是上周。我在奥尔登堡计算机博物馆里的 Commodore C64 上做怀旧程序演示。这不算。所以丢掉 goto 吧,但是给我 foreach。除此之外别无所求。JavaScript 的 with 语句我从来没有用过,不过这也是个好主意,我想这是某种形式的“少即是多”。(例如:所有的脚本语言在这方面都很出色)

事实上,有一些东西在其他地方还从未见过。之前,我遇到过许多循环,每个循环的入口和条件测试/循环退出都不相邻。那么如果有一种方法来表达一个循环从中间某个位置开始,看起来会很酷。否则,就需要复制循环的一些部分。有点类似 Duff 的设备,不是为了优化,仅仅是让代码不要那么冗长。

10. 表达式语法

许多人刻意避开,但是 ?: 三元运算符是个好主意,如果使用正确的话。Python 实现了 foo if bar else baz,有点罗嗦,不过也还算 OK。然而,JS 和 Perl 的布尔运算符 AND 和 OR 不是用来计算 true 和 false 的,但是对于特定的值可以被认为是 true。假设赋值 value = cmdline_option || “default”。这需要在所有数据类型上有恰当的布尔含义。

11. 表达式的函数性质

如果我想要写 Lisp,我会这么做的。我并不需要一个完整的函数编程语言。但是没有什么比 map() 更好的了。闭包和匿名函数(lambda表达式)也是杀手级的功能。可能“超级复杂”领域是 hyper 运算符(在 Perl6 中是这么叫它们)如 any() 和 all() (在 Python 中是这个名字),但是它们都很棒,并且暗含着并行的含义。欢迎来到新世纪,至少是九十年代吧。

12. 对象

已经有若干面向对象的模型,但是至少需要封装和多态。有一些组合类的方法也同样存在,如继承和混合。接口应该有,或者用多重继承来代替(译注:多重继承?梦魇,绝对的梦魇!)重载是很重要的,或者至少给我默认参数。命名参数也是很酷的办法。

13. 并行

对于这种情况我是个失败者。generator 和协程在某些情况下适用于此。要点是不要内建的多线程,但是让多个代码方便的在一起工作。它们不需要同时执行,但是应当可以同时工作于数据集的多个部分。将应用构造成事件驱动应当比较容易。这一机制对于真正的多核来说,非常棒。(例如:Perl5 的 POE,python 的 generator)

14. 字符串和 Unicode

这是该死的 2011,除了 Unicode 什么都不需要。所以务必包含安全的字符串类型,并且所有都是 Unicode 的,不要有例外。我对额外的双编码的隐式转换感到恶心,也不想再见到,或着手工转换而不是语言支持。顺便说一下,我更喜欢 UTF-8。谁会在意常量字符串的索引呢?在这种特例下用字符数组吧。一般情况使用正则表达式。在原始字符串的 API 上要有正则表达式支持。

15. 底层接口

有观点认为,可能会需要手工操作 bit。特别当目标是微控制器或嵌入式 ARM 内核,应当有方法吓到裸设备去。理想情况是用语言直接编写 OS 内核,而无须任何汇编(除了平台特定的启动代码,它们没办法用其他办法完成)。

———————–翻译分割线———————–

这文章实在是太长了,拆分吧。今天的任务到此结束。

To be continue…

Join the Conversation

11 Comments

  1. 楼主的中文不好,翻译的不是很容易理解,语言组织得没有中文习惯

Leave a comment

Your email address will not be published. Required fields are marked *