最近几周先后游览了Julia、Rust、F#、Go四种编程语言,了解了javascript和vue的一点前端知识。说是游览,是没写代码光看文档,当然必定是很浅的。肯定有很多错误见解,欢迎斧正。
Julia看起来是一种静态和动态混合语言,动态类型采用RC,能推断出类型的地方都用静态,静态搞不定的地方好像是自动装箱成动态类型,需要速度的地方可以获得和C语言一个数量级的性能。我觉得Julia的这种混合模式是未来主用语言发展的方向,静态必定有动态上的局限,而没有限定的动态又必定有语法检查和性能上的问题。但是为什么我又放弃了Julia语言的学习呢?那是因为Julia语言缺乏取代python、java这类语言的雄心大志,开始为了迎合用户,语法过于偏向,导致入门难度也不低,生态也没有起来,轮子不多,现阶段没有去跟的必要。具体一些小细节来说,该语言采取函数式的一次赋值,实现却区分值和结构类型有拷贝和引用不同的语义,一次赋值对保证程序正确性变得缺乏意义,另外该语言还没有实现线程安全。
Rust语言我就不多说了,我觉得生命周期是很好的概念,但是正当我打算深入啃一啃这个据说学习曲线很陡峭的语言,我先调研下准备用的场景,其中一个是Web编程,发现几乎就没什么人拿rust来干这个。为什么不能有一种语言适合大部分用途呢(除了写驱动程序)?不要和我说这不可能,在90年代确实不可能,但现在已经有了可能,未来一定会证明这是一个陈腐的观念!Julia语言就提示了这种可能性,当然它没有往这个方向发展。
然后是F#,我一直对lisp有仰慕之情,当然属叶公那种,但是一想到据说篇末要写一整页括号就止步了。然后突然看到一种几乎不用括号的正宗函数式语言,格式很优雅,也很简练,好像还能适合很多用途,还能跨平台,据说程序正确性很有保障----这点我相信,自然很有好感那是必定的。但是为什么又从游览到放弃呢?还是因为M$,考察了下应用场景,那些据说能做,也就是能做而已,轮子也不多,关键这些对掌握C#有基础的还是适用的,但是我一直就有抗拒心理没学,现在还是不想为此再学一种M$的C#语言。
最后把目光转回Go语言,这个被王垠吐槽说惹恼了他的语言。首先映入眼帘的是那个err!=nil错误处理方式,这个方式是两头不讨好,要求低的嫌麻烦,要求高的如王垠,觉得和java比失去了强制完整检查错误的可能性(这点我觉得从软件工程的高要求来要求语言是对的)。再看评价,几乎一片好评,感觉应该很实用,据说入门很容易,还有说用不用都值得一看,那就算放弃了损失也不大。最后考察应用场景,web有轮子,可以用;GUI,能做,但也只是能做而已;安卓,有可能,好像除了做游戏别的其实不行。好吧,打算入门看看,最后问了一个搞点编程的同事一句,他说你学go干什么?php也不象你想的那么差。我说我一直觉得php是套了C外衣的BASIC,一直不喜欢。但是他一句话点醒了我,我打算做个网站,是前后端全我自己包的,十几年没摸了我还是再研究下前端吧,一看发现世界完全变了,前端要学的东西太多了,我还是别花时间学什么go了,先php将就着用再说吧,以后再考虑。
对前端的了解和本文关系不大,就不细说了,只说点感想,那就是前端实在太乱,无数个轮子,无数个坑,估计前端很多时间都花在筛选轮子和填坑上了。轮子太多,并不等于好,肯定是没有完美的轮子才有很多人去造,只能说,前端是建立在一个沙滩上的,现在这样已经很不错了,只能怪操作系统的GUI太不争气,做出来的根本不能和Web效果比,搞得Electron这样的Web APP大行其道。时无英雄,使竖子成名。未来谁要想做一个一统天下的编程语言,功夫还要在诗外:一是要把标准库和库管理搞好,避免无谓造轮子,一个是要把GUI搞定,不要觉得搞点简单的GUI就能满足用户需求,GUI一定是一个在不断发展变化的东西。
编程语言的最根本分类是静态还是动态,一般都把讨论焦点集中在类型上,其实我认为动态语言和静态语言最根本的区别是能不能很容易的做出eval函数?也就是本质上能否动态产生运行时自我修改的程序代码?当然编程语言图灵机等价的本质,如《黑客与画家》所言静态语言必定可以弄个粗制滥造的lisp解释器来实现动态特性,但这不是我们想要的。字符串拼接无疑是最强大的动态代码生成手段,甚至有了JIT理论上必要时也有可能让性能不是差到不能容忍,但这也不是我们想要的,因为拼接的话语法和运行正确性都无法保证。我怀疑函数式编程的很多技巧,其实都是为了不用字符串拼接来生成可自我修改的代码的。
比如闭包,其实只是把函数的几个入参固定在运行时生成一个新的精简版的函数,那我们还有没有办法在运行时把一个简单函数升级成一个复杂函数呢?在操作系统层面,有DOS的int21拦截,有挂钩,在编程语言层面,我还真不了解,当然想要的肯定不是函数指针那样的简单切换。对于一个好的编程者来说,编程就是一个反复复制又反复提炼的过程,把一段代码复制修改,用到稍微不同的场景,复制多了觉得可维护性不好又把这些代码共同的部分提炼出来作为函数这是一种办法,另一种办法是把多处复制修改的代码综合成一个大的函数内部根据场景分支处理,闭包为这种方式提供了一定的便利性,当然提炼、合并本身也是个耗费时间的过程。
我个人认为自从1972年的Prolog之后,编程语言就没有本质上的突破,只有技术上的更新。这是什么意思呢?好比假定现在机器学习停滞了(如《三体》说的没有粒子加速器物理学没法突破了),自动驾驶、机器人送快递等很多代替人类劳动这些将来也必定能实现,只需要再深入改进现有技术就能达到,但是自然语言和人对话,那就是另外一回事了。命令式语言,只是一步一步叫电脑怎么做,这种入门门槛最低;函数式语言,只是告诉你,欲练神功,必先如何,你要算A,先去求BC,就触发一个递归,一步一步套下来,其实展开看,和命令式一样(我觉得函数式最简单的理解就是用变量名替换把嵌套的公式展开),但是通过函数式抽象定义,代码去除冗余要精简不少,逻辑性增强,水平又上一个台阶,当然门槛也高了。而理想的方式,是我们先定义一个模型,再告诉电脑我们要求什么,Porlog看起来似乎是这样,SQL表面上看似乎和Prolog等价,其实不是这样,因为在SQL中,表与表、列与列之间的关系,是存储在程序员的大脑中,电脑根本不知道(除了极个别约束),电脑收到的只是一条条SQL命令,完全不知道你真正要求的是啥?也就根本没有办法从现有的SQL语句中根据你的需要构造一个新的查询。
为什么Porlog看起来很好,现实世界却用不开呢?除了现实世界是个不确定性的世界,加上概率后,逻辑推理层次深了就不准了,更多感觉还是语言本身的问题。如王垠说的只用深度搜索一种策略,那么用广度搜索的逻辑程序语言可行呢?显然也不够,深度加广度呢?肯定也不够,不然就有人做出来了。现实世界比我们想象的复杂,我们需要更多的算法,但可以让计算机替我们选择优化,其实之前也有,比如SQL的执行计划优化,可以根据被执行表的数据分布情况选择不同的查询策略,语言编译和执行未来也可能这么做。但是在这些之前,我们需要一个更有效的办法把现实世界的模型给计算机化,假如实现了这点,我们就有可能把程序语言上升到Prolog许诺的那个层次。
关于现实世界模型化,举个简单的例子,比如你写 a=b+1,这只是一个简单的一次性赋值;如果这个是个函数式的定义,则是个单向的定义,对任何b,a都自动等于b+1;如果 a===b+1 这里是恒等的意思,则相当于双向绑定或约束,对任何a,b也等于a-1。当然 porlog 用的是一阶谓词演算,学起来复杂,但本质上比这个数学运算简单。谓词演算什么意思呢?其实我也不懂,所以我只好从非逻辑学的角度来解释下Prolog那样的逻辑式程序语言求解是怎样的?假定a、b都是正整数且小于10,如果给了b求a,当然你一下就算出来了,如果给了a=9求b,你怎么办?假定你的计算机不懂数学公式推导,你只好硬算,b从1数到10 ,看哪些值让等式成立(为真)就放到结果集中,这也许就是王垠的教授老问他能不能反向的意思。如果a、b是实数怎么办?我想和你说计算机它算不出来,但是且慢,我们在计算机里面算的数都是有精度和最大大小的,所以还是可以遍历的,哪怕算到地老天荒,从可计算性角度你不能说算不出来,当然这样有问题,比如sin函数,你遍历精度,可能本来用反函数arcsin能算出来的值正好跳过去就算不出来了,但这是模型的问题,不是计算机的问题。从另外一个角度说,计算也就是在可能的结果集里根据条件对可能结果剪枝,好比关系式数据库一做关联查询,首先把几个表乘起来弄个笛卡尔集,然后再削减,起码理论上是这样定义的。数学公式的反向推导和求解,那是数学范畴的,不是计算机范畴的,虽然做出来可以直接拿起来用。但是怎么很好的定义现实世界的数据间的关系呢?我要知道我就去做了。但是有一点我可以肯定,把程序员脑子里的模型尽量放到计算机中,既可以节省程序员的劳动,又给计算机自动优化提供了更大的空间。
回过头来看,函数式虽然看起来很优雅,其实需要把每步欲求什么先做什么都定义好,这样才能把整个链条串起来,不然就拎不起来了。而逻辑式是把现实世界模型化抽象化的关系和约束输入进去,求可能的结果集,是一种开放式的求解。但正因为是开放式,一种算法甚至几种有限的算法就不够应付各种情况了,但是方向还是好的。再举个例子说,比如一个复选框,最简单的逻辑定义就是这一组选项只能有一个为真,这样的定义比收到一个点击事件就循环把其它项都复位要简单多了。
最后要说一点,象Rust、Julia这些最近15年才出现的新型语言,跨平台的背后功臣是LLVM,我相信长远的未来,LLVM必将取代JVM,几十年后的未来一定在新型语言中产生,现有占据前列的所有旧语言一定会落下宝座。