首页 / 软件开发 / 数据结构与算法 / 如何设计一门编程语言(四) 什么是坑(操作模板)
如何设计一门编程语言(四) 什么是坑(操作模板)2014-08-23其实我在写这个系列的第三篇文章的时候就已经发现,距离机器越远,也就是抽象越高的概念,坑的数量是越少的。但是这并不是说,距离机器越近的概念就越强大或者说越接近本质。这是广大的程序员对计算理论的一种误解。大多数人理解编程的知识结构的时候,都是用还原论来理解的,这个方法其实并没有错。但问题在于,“还原”的方法并不是唯一的。很多人觉得,反正你多高级的语言编译完了无非都是机器码嘛。但是还有另一种解释,你无论多低级的语言编译完了无非也就是带CPS变换(continuation passing style)的λ-calculus程序嘛。他们是等价的,不仅能力上也是,“本质”上也是。一个用CPS变换完整地处理过的λ-calculus程序长的就很像一串指令。而且类似于C++的inline操作,在这里是完全自然、安全、容易做的。那其实为什么我们的机器不发明成这样子呢?显然这完全跟我们想如何写一个程序是没关系的。正是这种冲突让我们有一种“概念距离机器越远运行速度就越慢”的错误的直觉。扯远了讲,就算你在用一门函数式语言,譬如说Haskell也好,F#也好,最终在运行的时候,还是在运行彻底编译出来的机器码。这些语言是完全不需要“模拟器”的,虽然由于各种历史原因人们首先开发了模拟器。当然一个精心设计过的C程序肯定还是要比haskell快的,但是我觉得能这么干的人不多,而且大多数时候这么干都是在浪费老板的钱而已,因为你们的程序原本就不需要快到那种份上。这种东西就跟那些做互联网对于测试的想法是一样的——有bug?发现了再说,先release抢市场。如果对这方面有了解的话,CPS变换——也就是Lost In Stupid Parentheses-er们最喜欢的call-with-current-continuation,他的另一个名字叫call/cc——是一种跟goto一样强大而且基本的控制流的做法。goto和CPS可以互相转换不说了,所有其它控制流都可以转换成goto和CPS。它们两者在这方面是不相上下的。而且既然一个完全用CPS变换处理过的程序长得就像一串指令,那你说他们的区别是什么呢?区别就是,CPS可以是强类型的,而goto则永远都不可能。作为废话的最后一段,我给个小例子来讲什么叫“一个用CPS变换完整地处理过的λ-calculus程序长的就很像一串指令”。就让我们用a(b( x ), c( x ))这样的一个表达式来讲:
处理前:a (b x) (c x)处理后:b x λa0.
a a0 λa1.
c x λa2.
a1 a2用我们熟悉到不能再熟悉的Haskell的Monad的手法来翻译一下其实就是:a0 <- b(x)
a1 <- a(a0)
a2 <- c(x)
return (a1(a2))好了,至于上面这种形式(看起来很像SSA)是怎么被做成机器码的,大家自己去看编译原理吧。上面这么多废话就是想表达一个结论:抽象并不意味着负担。当然,至于对程序员的智商上的要求,对某些人也是一种负担,这个我就没办法了,所以就不考虑他了。===============废话结束================模板也是这类抽象的一种。为什么我要把标题写成“坑”,只是想跟前面统一一下而已,其实到了模板这么高级的抽象的时候,基本上已经没什么坑了。当然C++的做法就另当别论了,而且我想那些坑你们大概一辈子也碰不到的了。那我们先从简单的讲起。比模板更简单的东西自然就是泛型了。为什么叫他泛型?因为泛型实际上就是一种复制代码的方法,它本身是没有推导能力的,所以肯定谈不上什么模板了。但是在大多数情况下,泛型这么弱的抽象也已经基本够用了。跟泛型相关的手法大约有三个。第一个就是定义一个返回一个类的函数(在这里参数是T):class Array<T>
{
public Array(int count);
public int Count{get;}
public T this[int index]{get; set;}
}