皇上,还记得我吗?我就是1999年那个Linux伊甸园啊-----24小时滚动更新开源资讯,全年无休!

Clojure太灵活,我们能如何驾驭它

作者 何婧誉(Loretta)

古话说的好,静若处子,动若脱兔。这个我觉得非常适合形容动静态语言的区别,静态语言因为类型系统的关系,一直给人的是很稳定、很可靠,但是可靠到一定程度就变成了死板,会变成一个牢狱或者困住业务上所需的灵活性,因此常常需要很多层抽象,很多层胶水代码,代码就开始变得非常的晦涩,非常的难懂,而动态语言则完全是相反的。

引言

这个题目非常的生动活泼,但是下面可能有点干(货)。上次来Qcon是两年前的事情了,在上海,那次我对Qcon是做了一个比较笼统的这样的一个介绍,所以这次主要来讨论一下动静态语言的问题,这个问题争议非常的大。

Morgan Stanley公司在国内的技术圈可能不像在英美那边对投行特别有认同感,大摩的技术文化跟团队协作,跟我之前的四个东家相比的话,并没有差到哪里去的,而且他做得东西也非常有意思,大摩现在是全球前三大的Scala的公司,公司内部也做了很多编译系统的改进,我们大概有250万行Scala代码、三四百个Scala程序员,内部做得东西也是非常有趣,是做一个很纯函数的系统,是为了解决分布式计算不够简洁的问题,即内部的函数基本上全部都是纯的,基本上没有负作用,这样的话,在什么地方执行都是完全没有关系的,反正结果都是一样的,那么在这样的理念下面,你可以想象250万行Scala,我们做出了双时效数据库,做出了很多支持函数理念的东西,这个平台理论上是非常先进的,所以我在大摩的感觉也是天天可以学到很多东西。

1. 静态语言VS动态语言

静态语言因为类型系统的关系,一直给人的是很稳定、很可靠,但是可靠到一定程度就变成了死板,会变成一个牢狱或者困住业务上所需的灵活性,因此常常需要很多层抽象,很多层胶水代码,代码就开始变得非常的晦涩,非常的难懂。动态语言则完全是相反的,所有东西都是从类型上来讲,以函数为例,灵活性已经足够了,但是通常我们写着写着就忘记数据长什么样子了,你可能今天写了一个函数说,输入一个函数的数据,然后过了一个星期之后,我已经完全忘记这个数据是什么东西了,因为生产环境里面,类型系统在没有编译器的帮助下,基本上都是一次性的,这个问题对于用户来说有相当大的困扰。一直以来,这两派之间没有争出特别的高低,静态语言笑动态语言做不出大系统,动态语言笑静态语言写的太慢、废话太多,今天这个主题当然不可能解决这个纷争,但是希望通过Clojure这个语言可以给大家一些不太为人知的思路。马上就有人来问了,我写Clojure就是为了逃避这样的内容来写系统,这样灵活多好用啊,我想写什么就写什么,快速原型靠的就是这个,我非常同意这一点。

2. 简单示例

Clojure太灵活,我们能如何驾驭它

在Clojure里面有一个json,因为动态语言的关系相当的简单,完全没有废话。这个函数我觉得哪怕是不写Clojure的,这个也是应该很能读的懂的。首先有一个Java的Reader,是FileReader,这个Reader被传递到了这个json的函数里面,读出来文件内容,读到Map里面,但是读完之后,你知道数据长什么样吗?不知道,下次换一个json文件,同样的函数可以同样读,但是你不知道读出来是什么东西。讲到这里就已经有一点难度在里面了。现在看一下,我现在读完了要处理,我处理之后,我写任意一个函数,如果说你不看这个函数写的什么东西,你知道它处理完成之后长什么样吗?不知道,你知道他希望这个json数据是什么样的形状吗?不知道。我现在看了代码之后,可以给你讲,它里面会有会有Age、Name、Job、Address。

Clojure太灵活,我们能如何驾驭它

看一下Age,它需要能够使用Int,那应该是个整数,但是要看代码才知道,再下面还是简单,那你们觉得Name的值是什么东西?完全没有使用到,它是一个String,它是不是姓和名放在一起了?还是放在一个Vector里面,可能姓和名是分开的,就是说不知道,要看代码才知道。

Clojure太灵活,我们能如何驾驭它

你看到代码之后觉得,原来是这样,它应该是一个Vector,或者是List,姓和名是分开放,因为它这系,它用空格来Join一下,这个是一个很浅显的例子,就已经说明了Clojure的动态灵活性非常强,但是也造成对数据的解释性标记不是很清楚。

刚才是一个很浅显的例子吗,现在来看一个更具体的。为了这个主题想了好几天,觉得还是写一个很小的项目来展示一下我今天要讲的东西,那写什么东西呢?我又想了好几天,最后的结果是,先谢谢链家,因为是这样的,这个既然要来北京,就要关注一下房价,这个大家都在很关注房价。那我就到网上去看一看二手房,然后一页页翻过去很累的,我不可能就是这样手写一个总结,那我就写点程序把它抓一下,当然这个不是真的写了一个爬虫,只是抓几个页面做做样子,没有让链家服务受到伤害,请鸟哥放心,我不知道鸟哥在不在,可能不在。主要是这个命名空间,它做的事情基本上就是通过一个库把html读进来之后,它会进行一些简单的操作,把这个整理好的这个数据写到一个EDN文件里边,比如说第一条你可以看到这个小区,然后1150万,三卧室两个客厅,一个厨房两个卫生间这样,面积之类的东西,那么我们看到这个数据转换的这个函数,它收到一个参数是Page,但这个Page长什么样完全不知道,我是通过库读进来的,读进来之后,我不知道它长什么样子,我现在看这个代码也非常难知道,它到底会返回一个什么样的类型,什么样的数据,如果将来需要扩展的话,或者将来我要给另外一个人用,或者帮助另外的一个人去做一些扩展,做一些维护是很难搞定,这就是我刚才说的Clojure作为一个动态语言的弊端,就是太灵活,导致经常会忘记这个函数的参数是长什么样子,而且这个是小项目,项目一大,那就更麻烦,那我知道你们有人会说的,说这个文档不就是做这个事情的吗?文档跟测试,但是文档它本身的代码是剥离的,它没有紧密的联合在一起,而相对代码本身是没有限制的,所以完全有可能你们自己有经验对吧?比如说很多代码上面会写,但是其实代码里面并没有,它可能起到的效果某种程度上也是挺有限的。

3. Core.typed

Core.typed是一个类型系统。它和其他语言的类型系统还是有点不一样的地方,不同点在于它不是语言的一部分,而是一个即查即用的一个库,就是说Lisp灵活性导致它能够作为一个库直接插进去,而不是要作为一个语言核心。因为它有宏,通过宏可以把一个很大的类型系统直接插进去,而且这个类型系统比一般的系统要灵活很多,主要体现在这几个方面:

第一,它可以给已经写好的,没有标注过的,或者说是用的库里面没有标注过的函数直接加上类型;

第二,不需要把所有函数全部加上类型,你不想要的话,就不需要;

第三,你即使加上了也不需要一定要进行类型检查,所以它是一个非常选择性是非常强的一个东西,因为它是为了能够和Clojure这样的语言进行协作,那么最后一个为什么要这样,为什么要加类型?然后不进行类型检查呢?有时你用库的时候,你给一些要用到的函数加了类型之后,不想要这个东西进行类型检查,因为一旦检查就要把这个库全部都标上去,库面里面所有函数全部都加上类型是很累人的。那我们现在看一下它支持什么东西,OptionType,现在很流行,这个流行的语言现在都有这个结构。Ordered Intersection Type这个我不多讲了,这个就是说一个函数,比如有两种参数形式,这两种参数类型可能又不一样,你再进行类型检查的时候,它会把这个参数从上到下有序的来进行一个匹配。unionType,写过Haskell人都知道,这个很简单,比如说整数,或者说是字符串,把它union一下,那就表示这个类型里面的东西可以是字符串,也可以是函数。

Identity是很简单的函数,它会给你一模一样的东西,那它的类型是什么呢?它这个函数的类型是什么东西呢?它这个函数的类型是,可以看一下,让Core.type来帮我看一下。这个基本上可以看到前面有个all,在这里对所有的X能取得的类型它返回的是一个X,就是Polymorphism最简单的一个体现了。Occurrence Typing这个东西见到的比较少,它是什么呢?它是通过检查代码里面写的控制流,比如像if,或者像switch,它能够进行类型推导,我可以给你们举个例子,那首先呢,把这个Form绑到A这个名字上面,值就是1,但是我把它标注成了any,就是说这个A,就算只是1,然后再返回A,这里大家觉得会返回什么东西?如果是检查一个类型,它最后返回的是A,它是什么类型?Any,因为我已经标过了,我说A是Any,所以它就相信A是Any,但是如果我这么写,这个会返回什么东西?你可以看到它现在还是返回的是A,这个A或者这个也是A,那其他情况返回的是Nil,那他现在觉得这个东西是什么呢?还是不是Any,因为你现在有了控制流在这边,代码里已经写过了,所以它知道你只可能是number,或者是string,要不然就是nil,所以最后A是union string/number/nil。这个东西功能上是非常强大的,这个也是我强推的一个东西,这个你真正用起来就知道方便。

最后一个就是宏也会被展开之后再推导类型,宏跟大家刚刚知道的switch有点像,就是已经很直接了当的,告诉大家这个宏是可以展开之后判断类型,然后给大家看一下我做的这个Demo的这个types Demo,就是把刚刚链家那个小项目加了类型系统,就是说我现在是把它所制造的结果定义类型,然后它其实是什么呢?是一个Map。

Core.spec总结

  • 通过一个库给动态语言加上类型系统——即插即用
  • 可以给已经写好的函数或者是用的无类型库标注类型
  • 可以选择性地加上类型
  • 加上了类型也并非一定要type check
  • 支持Option Type,Ordered Intersection Types, Union Types
  • 支持Heterogenous Maps和Sequentials
  • 支持Polymorphism (All, Context Bounds),Higher-Kinds
  • 支持Occurrence Typing!(通过检查control flow进行类型推导)
  • 宏也会被展开后再推导类型

4. Core.spec

我本人是很喜欢写这个东西,我觉得给函数加上类型是非常过瘾,但是有问题,那有别的办法吗?有的,Core.spec,现在这个东西是Clojure核心,在很尽力地推广。在方法上或者在函数上,加上先限条件,它会稍微更加,功能要强大一点,因为它强大在什么地方呢?

第一,生产环境,Runtime不会受到影响,它的性能不会受到影响,因为如果你一天到晚在检验,它的性能上是会受到影响的,所以缺省验证是关闭掉的,那如果你觉得某些东西可能重要性比较大,你要加上去也是可以的。spec非常灵活,它可以把那种正则方式的rule给写起来,就是比如某个list,我觉得里面开头至少有一个字符串,然后后面跟着的至少是0个的整数等等,你就可以用正则里面的加号,星号直接定义这个rule。并且所有只有一个参数的predicate的函数统统可以跟它进行无缝对接,不需要另外语法把它转换成spec。这里面有很多种的验证方式,那么多的验证的方式可能现在没有时间讲,就不讲了,总体来说就是可以把数据套在一个很灵活的模子里。

Core.spec总结

  • Runtime性能基本不会受到影响(缺省spec验证关闭)
  • Map的类型应该就是key及其对应的值的类型!(keys)
  • Sequence可以多方面限制(cat, alt, regex style matching, coll-of)
  • 只有一个参数的返回boolean值的函数通通都自动成为predicate
  • 各种验证方式,满足你的需求(conform, explain, valid?)
  • multi-spec支持更复杂的数据结构

5. Core.type vs Core.spec

Clojure太灵活,我们能如何驾驭它

Clojure太灵活,我们能如何驾驭它

总结

core.typed和core.spec你推荐哪个?

我的脑子喜欢core.spec,因为有前景。我的内心喜欢core.typed,因为给东西加类型写起来真得很过瘾。

作者简介

作者何婧誉(Loretta),Morgan Stanley VP。我是一枚剑桥大学计算机科学系毕业的妹子,平时爱好各种新鲜事物,不会的都想学一点看一点。兴趣范围从技术、数学、金融到桌游、国标、英文书法、语言学、哲学、钢琴等范围极广,属于样样都知道一些的典型 jack of all trades。有收集德式桌游及大型乐高模型的癖好。

技术上主要擅长 JVM 语言,有几年 Java 经验,2010 年遇见 Clojure 之后顿时被其简洁的语法、简单的写法及极具表达力的特性深深吸引,2011 年得以开始专业 Clojure 5 年多,现于大摩写 Scala。主要用 Clojure 做数据流处理,但也曾用其做过网络应用乃至安卓应用。JVM 之外亦与 Python、Perl 等主流语言,以及 ML 等非主流函数语言打过交道。

约四年前开始与国内的 Clojure 社区有所接触,业余时间致力于解答 Clojure 相关问题,并希望能将 Clojure 的影响范围继续扩大。

转自 http://www.infoq.com/cn/articles/how-to-master-clojure