Programing Language Impressions

Impressions of the programming languages I have come across

C

老登语言,不如说我非常难以理解为什么这玩意几乎就是个汇编的封装器却会成为各大高校的入门教材……?

C 语言根本谈不上什么设计,因为它可以说就是没有设计。它就是个被造出来的人类可读的汇编简记法,然后碰巧火了而已。所以我不承认什么 C 的优秀,C 优秀根本就不在于它是 C,而在于用 C 写的那些 killer app,比如 UNIX。

的确,实事求是的说,C 语言是最通用、最可移植的语言,但是这并不是因为 C 的设计优秀,恰恰相反其实是因为 C 就是一个汇编的封装,可移植的其实不是“优秀的语法”,而是汇编。如果你要说 C 优秀,那么汇编也优秀。

C++

其实 C++ 挺遗憾的,C++0x 几乎就是个废物,在 90 年代末相对高级的语言群魔乱舞起来的时候只有 C++ 在那里不思进取吃老本,终于等 C++11 出来了挺大江山已经流失了,笑

你很难找到一门语言又进步又过时,既简单又难……除了 C++

可以说 C++ 其实很 fancy,它融入了很多新概念,但是它融入得不够好。都怪那该死的兼容性(乐)。到现在 C++ 的语法可以称得上一句沉重,但是沉重中又没有多少用得上的……

举个例子吧 std::optional<T>. 这玩意好吗?太好了。Null 的发明者都亲口说 null 完全是一次数十亿美元级的错误了。而我得说 C++ 简直就是这场错误的集大成者。C++ 里到处都是类似 Null 的东西。拿特殊的值标记无效的内存区域。灾难中的灾难。

为此明明有 Rust 可以学的,结果 C++ 就学了一个皮毛——把 optional<T> 抄过来了,然后就不管了。std::map 都有好多不同的取值方式了,什么 [] 在 key 不存在的时候返回默认 constructor,什么 at 抛出 std::out_of_range 异常, find 返回 end 迭代器,妈呀结果就是没有用上 STL 自己的 optional<T> 的——excuse me?所以你弄一个 optional<T> 是为了走时髦吗?

至于 C++ 很难的问题,其实倒是可以接受,因为 C++ 简单——你可以同时觉得 C++ 简单和难得要死。听上去很离谱,但是是 C++,倒也正常.png

C++ 就是那种非常适合算法竞赛、非常适合学生上课的语言。反正内存安全去他爹,裸指针乱飞,嵌套结构稍微学学就会,性能还超级高,还封装了一堆常用数据结构,它不火谁火.png

但是复杂度摆在那里。如果要简单,那就一定有复杂在反面等着你。一门面向底层的语言,讲究极致压榨性能极致压榨空间甚至到处都是直接操作内存、还有超级多历史包袱的语言居然还能做到让一个初学编程的人轻松写出一坨狗屎也能通过编译,那要写出“优雅”的代码它肯定会很难,这一点也不难理解吧。就是它有点难过头了而已。

很难说如果 C++再这样赶时髦地加入新功能下去是不是某种自取灭亡,只能说希望不是吧

以下是说怪话时间

C++ 真是一门优美简洁的语言啊 真是优优又美美啊

你看别的语言还在 <T extends U | V> 真是难难又懂懂啊 extends 是什么?竖线又是什么?真是不说人话让人以理

再看我们 C++ 啊跟念诗一样

template<typename T>
requires (std::is_base_of_v(T, U) or std::is_base_of_v(T, V))
bar foo(T&& xyz);

你看我写了一个 T 啊当然是模板啊 又 requires 啊 is base of 就是基类啊 用 or 取代竖线英英又语语啊 哪怕是不懂 C++ 的人看到也能马 ↑ 上 ↓ 理解这是在说什么呢

真是容易理解呢呢呢这就是我们 C++ 啊真是 CC 又加加啊

Crystal

截止到本文的上次编辑 Crystal 还是一门非常初生(?)的语言,嗯就是语法描述也不全,生态少得可怜,连编辑器提示都没有,各种地方都透露着种种不成熟

但这玩意确实是个静态、native 语言,还是 native 语言中的异类,在 native 中做到了可以尽可能地少写类型,而且居然还有类 Ruby 语法,弄得这语言跟个动态语言似的

非常喜欢的是 Crystal 继承自 Ruby 的一个特性,你可以随意 Reopen 一个 class 然后添加进去自己的方法。甚至官方库都是这样做的,比如 JSON 库:它直接把 int 打开了然后往里面塞了一个 to_json 的成员函数,然后所有 int 就能 to_json 了,非常的好用

struct Int
  def to_json(json : JSON::Builder) : Nil
    json.number(self)
  end

  def to_json_object_key : String
    to_s
  end
end

这个真的很妙,虽然它有一个众所周知的缺点,要克制,小心和别的库添加了冲突的成员函数。但是相比之下这个带来的简直是致命的诱惑,可以通过 reopen 创造非常具有表达力的语法,就像 Rails 做的那样。

Elixir

TODO

Haskell

纯函数式编程教科书级别语言。

Javascript / Typescript

JavaScript 系的缺点太多我觉得没必要说了,毕竟一个 10 天写出来的语言,蹭 Java 的热度,然后意外成了互联网标准这种事情……只能说这就是现实()

简单说些不好的地方:

  • 没法重载运算符,写带计算的代码很丑陋(不过作为为网页服务的语言,遇到大量计算就上 WASM 了
  • 你永远不知道是坨什么的 this
  • 到现在都没扯皮出公认的标准,都是 ES6 和 CommonJS 分立
  • ==

JavaScript 系最大的优点大概是非常原生非常友好的 async/await。单线程的设计又让你根本不用在乎资源加锁之类的异步噩梦。

而且 JavaScript 可能可以说是流行语言里最把 lambda 函数发扬光大的语言。也是动态类型语言里类型标记做的最好的——它甚至演化出了专门的 typescript

也是因此,TypeScript 的类型系统实在是过于完备了,以至于大家仍然某种习惯于 JS 写法然后通过复杂的体操来保证 JS 写法是类型安全的,这其实有点呃呃

并且 TypeScript 其实是 null 不安全的,比如说

const a: string[] = [];
a[1]; // ts says string, but actually undefined

typescript 不检查 out of range 的可能性,以至于当你给出 string[] 这样的类型的时候 ts 会认为任何一个 number 作为 index 传入都能得到一个 string —— 但致命的是,实际上翻译出的 js 它可能是 undefined

这个非常的坑,可以说是 TypeScript 上巨大的一个洞,直接导致 undefined 可以打穿 ts 的类型系统。可惜木已成舟便是了。

Julia

TODO

Lean 4

TODO

Lua

TODO

Python

Python 的火爆完全来自于优秀的库而不是优秀的语言设计,夸张点说 Python 完全展示了什么叫糟糕的语言设计硬被优秀的库带飞。

完全不觉得 Python 是很适合工程实践的语言。它更多的是一门教科书语言,类似以前的 BASIC,很适合初学者浅尝辄止地入门,但是稍微复杂一点就完全是噩梦,更何况 Python 的语言设计还很糟糕

我不喜欢 Python 设计者的审美,比如认为 lambda 函数没有必要可以用具名函数取代。也不喜欢缩进表示一个块。

并且 Python 真的不思进取。换其它语言如果有这么多优秀的库这么多资金绝对不会放任自己的性能烂成这样,但是 Python 到写下这一段的时候都还没有成熟的 JIT。

Ruby

相当优雅的设计,动态到叛逆。我对 Ruby 最大的好感来自它不试图用各种什么哲学什么风格指南约束程序员,如果 Ruby 有哲学,那就是最大的让程序员幸福

因此写 Ruby 代码确实很舒服——如果抛开过于动态导致的特别容易出运行时类型错误不谈的话……

Ruby 最使我影响深刻的或许是类似这样的代码

3.times do
  say "hello!"
  sleep 1.seconds
end

这是我在其他任何语言都没感受到的神奇的 magic,或者说其它语言当然也能写出类似的代码,但只有 Ruby 鼓励这样做,并且到处都是这样做,让整个代码特别具有“阅读感”,哪怕一个什么都不知道的新人看到这样的代码估计也能瞬间理解它在干什么,只是可能不知道具体是什么原理而已。

Rails 宣言有说

另一个例子仅用了些许代码实现,却几乎引发了惊愕的程度。Array#second#fifth(以及挑衅意味的 #forty_two)。这些别名的存取器,非常严重地冒犯了常发表意见的支持者,他们说:这简直太过度设计了(几乎是编程时代的结束),这些写成 Array#[1]Array#[2](以及 Array[41])不就可以了嘛。

但时至今日,主要的抉择还是,让我自己开心。我喜欢在终端或测试里编写 people.third。不,这不合理,也不高效。可能我有病吧,但这仍能让我发自内心地微笑,满足了这个理念,也丰富了我的人生,帮我在过了 12 年之后,还仍继续参与 Rails。

这个真的非常叛逆,在许多语言都在宣传“做事情只有一个最好的方式”(盯 Python),甚至直接在程序语言上拒绝和故意劣化某些风格的时候,Ruby 的想法缺是做事情可以有很多种方式,程序员来选择最让自己幸福的方式。大家都在把程序员当成 思路 -> 代码 的翻译工具,而 Ruby 选择将程序员视为作家。Ruby 的放纵让人感到某种幸福。

另外一个令我有些感动的是 Ruby 到现在已经逐渐不再流行,但是仍然在努力做出改善,Ruby YJIT 已经可以进入生产环境并且被 Rails 默认开启。以前 Ruby 最大的诟病是它慢,慢地出奇,比 Python 还慢,但是现在 Ruby 的速度已经逐渐追上的 Python。

说完优点,Ruby 一个很大的缺点是:你家语言三个闭包.png

在 Ruby 中有三种不同方式去声明一个闭包,我觉得这完全是没必要的,

Rust

虽然 Rust 社群的名声不好 语言原神,但是不得不承认的是 Rust 是一门非常优秀的语言。我对 Rust 最大的好感度其实不是内存安全性 —— 毕竟合格的程序员应该能用任何语言写出内存安全的代码 —— 而是 Rust 非常优秀的模式匹配和语法。

Option<T> 是现代且优秀的思路,我觉得所有可能带 null 的语言都应该引入它。而 Rust 在这一类功能上做得非常好。虽然 Result<T, E> 和异常的优劣可能有待商榷。但是大部分情况,Rust 的 enum 的设计做得非常的不错,完全改变了 enum 曾经只是个不蕴含实际信息的“标记”的情况。

如果 rust 没有内存安全作为梗小鬼的吹嘘资本,哪怕是优秀的语法设计也能让它成为一门优秀的语言。

Zig

better c,相比 C 而言有出色得多的设计。老实说我还没用过 zig,但是也许未来会试试。

但是语言的作者 Andrew Kelley 是个自大的独裁者,他宁愿以自己的看法去约束所有使用 zig 的人而不是倾听社区的意见,当然这在编程语言上不是很重要的坏事,但也不能说是好事

Andrew 拒绝引入闭包的时候如是说

I have rejected both of these proposals. In Zig, using functions as lambdas is generally discouraged. It interferes with shadowing of locals, and introduces more function pointer chasing into the Function Call Graph of the compiler. Avoiding function pointers in the FCG is good for all Ahead Of Time compiled programming languages, but it is particularly important to zig for async functions and for computing stack upper bound usage for avoiding stack overflow. In particular, on embedded devices, it can be valuable to have no function pointer chasing whatsoever, allowing the stack upper bound to be statically computed by the compiler. Since one of the main goals of Zig is code reusability, it is important to encourage zig programmers to generally avoid virtual function calls. Not having anonymous function body expressions is one way to sprinkle a little bit of friction in an important place.

Finally, I personally despise the functional programming style that uses lambdas everywhere. I find it very difficult to read and maintain code that makes heavy use of inversion of control flow. By not accepting this proposal, Zig will continue to encourage programmers to stick to an imperative programming style, using for loops and iterators.

很显然大家并不太为他的看法买账,截止到写作时这条评论收到的 👎 高于 👍 不少。

而我个人其实是函数式程序设计的拥簇,在我看来对指定的某个 shape 给出变形比给出生成变形的过程要容易理解的多,也更加方便编译器猜测意图。更何况 zig 其实可以编写某种匿名函数,只是样子极其丑陋:

fn bar(comptime x: i32) fn (i32) i32 {
    const res = struct {
        pub fn foo(y: i32) i32 {
            var counter = 0;
            for (x..y) |i| {
                if ((i % 2) == 0) {
                    counter += i * i;
                }
            }
            return counter;
        }
    }.foo;
    return res;
}

那么其实没有任何理由认为 zig 应该拒绝匿名函数这个非常成熟而方便的设计。

Previous  Next