从一道面试题看不同编程语言的表述能力

很多人都不明白,不同的编程语言具有不同的表述能力,这就是我们为什么有了汇编之后还需要更高级的编程语言。在软件规模愈发庞大的现在,C 和 C++ 语言表述能力的不足越发的明显了。我们应该尽可能的选用高级的编程语言完成我们的任务,在需要性能的地方,使用 Profiling,而不是在一开始就选用 C/C++ 这样的语言去工作。

最近一段时间,突然看到一个很吸引眼球的题,而这道题貌似 ThoughtWorks 也考过类似的内容。题目很简单,但是怎么写都让我觉得很别扭,知道有一天我用 DrRacket 写了一下,才恍然大悟。在没有模式匹配的语言中,这道题就是怎么写怎么别扭,没办法的。下面我们来分别比较一下 C 语言和 Scheme 这两种古老的语言的实现。

题目描述(大意):

依序遍历 0 到 100 闭区间内所有的正整数,如果该数字能被 3 整除,则输出该数字及 ‘*’ 标记;如果该数字能被 5 整除,则输出该数字及 ‘#’ 标记;如果该数字既能被 3 整除又能被 5 整除,则输出该数字及 ‘*#’ 标记。

首先先看看 C 语言的常规实现:

for (int i = 1; i <= 100; i++) {
if (i % 3 == 0) {
if (i % 5 == 0) {
printf("%d*#", i);
} else {
printf("%d*", i);
}
} else {
if (i % 5 == 0) {
printf("%d#", i);
} else {
; // Do nothing
}
}
}

省略了 main 函数等关系不是很大的部分。我选择逻辑最清晰的两层 if-else 的方式,比起其他“聪明”的方法,可读性最高,逻辑最清晰,最省计算量,同时也最容易添加、修改程序逻辑。

我们再来看看 Scheme 的实现:

#lang racket
(require math/number-theory)
(require racket/match)

(define (range-closed from to [step 1])
(range from (+ 1 to) step))

(let ([numbers (range-closed 1 100)])
(for ([x numbers])
(match `(,(divides? 3 x) . ,(divides? 5 x))
['(#t . #t) (printf "~A*#" x)]
['(#t . #f) (printf "~A*" x)]
['(#f . #t) (printf "~A#" x)]
[else (void)])))

经过对比我们发现,Scheme 语言由于具备(由库提供)模式匹配功能,从而使得程序的逻辑较 C 语言清晰不止一筹。仔细的总结一下 Scheme 实现的优点:

  1. 有意义明确的方法来产生一个闭区间的所有数字:range-closed
  2. 无需手工确定闭区间的左右边界:for 循环;
  3. 有意义明确的方法表示整除:divides?
  4. 有一致的命名规则表示返回布尔值的方法:后缀?
  5. 通过模式匹配,判断了被 3 和 5 整除的所有情况,和该种情况下对应的动作:'(#t, #t) '(#t, #f) '(#f, #t) else

我甚至还没有说 Scheme 中可以在方法中定义方法,而 C 语言不可以(GNU 扩展可以);C 语言其他类型可以被直接当做布尔值使用,以至于发明出了 NULL == ptr 这样扭曲的表达方式(但是我估计这里没有人想用 if (i % 3) 吧?)。

这只是非常简单的一道练习题,就能够暴露出了 C/C++ 语言的诸多弱点。我不是说 C/C++ 语言一点好处都没有,但是绝大多数情况下,我们应该选用其他具有更强表达能力的语言。

珍爱生命,远离低级语言!