RSS Feed

关于Typed Clojure

2014-08-17 by xudifsd

最近一直很忙,都没空写什么东西了。今天终于闲下来了一些,现在正值今年GSOC项目要结束,而且国内关于Typed Clojure的资料少得可怜,所以写点东西介绍一下。

Typed Clojure

Typed Clojure的作用就是给Clojure语言提供一个可选的类型系统,以获得与静态类型语言同等的类型检查能力,但是这种能力的获得仍然是需要一定的代价:那就是使用者需要为每一个全局变量以及函数都加上类型注释,现有的类型可以见项目的wiki。不过它确实能完成对Clojure的类型检查,甚至包括对nil或者java null的捕捉,这一点还是很帅的。

感兴趣的话可以看作者Ambrose在Clojure conj上的介绍视频(需翻墙),这个介绍讲的还是很清楚的。Typed Clojure完全通过Clojure的宏来实现,而不需要特殊的解释器或编译器,这也是Lisp类语言最大的优势,试想如果需要给python加一个类型系统,不自己重写个解释器是不可能。

Typed Clojure受Typed Racket的影响很大,很多理念都是借鉴自他们,甚至我今年的实现内容也受到他们的影响。就健全性来说Typed Racket要优秀很多,因为Typed Clojure现在就只有Ambrose在维护,贡献者也很少。而Typed Racket就有点像是一个学术项目了,它们的贡献者以及维护者大多是Northeastern University PLT实验室的博士或博导,他们大多会在这上面发论文,比如这篇介绍,就说“Typed Racket is not just a nice language … it’s also informing PL research at every level”。

我的工作

我今年GSOC的内容就是给Typed Clojure类型系统添加两个函数类型,让Clojure中一些特别奇葩的函数也能使用first class type做类型标记而不需要在类型系统内部特殊处理。这些奇葩的函数就包括assochash-map。因为这两个函数对参数还有特殊的要求:因为都是操作map的,所以要求接收的参数与返回值map的key和value的类型匹配,而且也要求参数是成对出现的。

Typed Clojure已经有了和Typed Racket类似的dotted type variables,这种类型已经可以用来表示不限参数个数的、任意类型的函数,并在结果上对参数类型加以限制。但是却无法表示要求参数成对出现,所以之前Ambrose的想法就是提供一个类似...2这样的语法来表示参数成对出现,不过这种做法有点类似将参数写死在类型系统里,如果之后出现了要求参数成三个出现,则又必须提供...3这样的语法。

在与Typed Racket的人交流后,我们决定使用最lisp的方式解决——使用list。简单来说就是给list类型加上一个:repeat这样的属性,让:repeat的list可以匹配与它长度成倍的list,例如,(HVec [Number String] :repeat true)可以匹配(HVec [Number String])以及(HVec [Number String Number String])之类的,并且给函数类型加上<*以及<...这样的语法,意思是将多出来的参数捕获并与它之前的类型进行匹配,它之前的类型一般是有:repeat属性的list,这样就很好地解决了写死参数的问题——多给list内部加一个类型就能支持参数成三个出现的要求了。

如果对细节感兴趣的话可以看下我写的关于我的实现细节以及Typed Clojure类型推导的文档

Typed Clojure现在仍然很不成熟,从版本号(0.2.66)就可以看出来,现阶段已经可以在一些小的项目中使用了,但是我觉得在真正的生产环境还是最好不要使用,首先一个原因就是它的类型检查还是比较慢的,再者就是他的硬伤——学习成本,需要在项目中使用就需要完完全全学会它的类型注释。

如何向Clojure贡献

Clojure以及任何Clojure官方库的贡献都要求签CA,以前还必须是纸质信件邮寄到美国,现在终于支持电子版了,内牛满面。不过签完CA后还不能通过github的PR进行贡献,必须通过Clojure的JIRA提交patch。这确实有点麻烦,但是Clojure也是通过这种手段保证不会有有版权代码的进入。

如果想要给Clojure或它的官方库贡献代码的话可以从它的JIRA中的issues下手,因为那里一般都是一些实现的bug,会有比较清晰的思路着手,并且能在这个过程中熟悉原有代码结构。

关于开源

今年参与Typed Clojure觉得最大的收获就是了解了下国外的开源是怎么运作的,我觉得这中间最重要的就是sponsor扮演的角色。很多项目开发者要不就是兼职开发开源项目要不就是全职开发了,比如我的mentor Ambrose就是全职在开发Typed Clojure,但是收入从哪来呢?就是通过sponsor,比如他就通过国外的众筹来筹得一些项目经费。

甚至在Typed Clojure还没开始时,sponsor就在扮演很重要的角色了,见。Ambrose就是这样参加了那次Clojure conj,并在这个会上介绍逻辑编程,在会下和别人交流时别人提到有类型系统的需求,然后Ambrose才开始着手实现Typed Clojure的。

国内貌似还是没有这样的捐助气氛的吧。


6 Comments »

  1. w君 says:

    (为啥之前没看到这篇?)

    我这个暑假也遇到了类似的问题。每个函数需要给出一个函数签名,用来做类型检查/推导。

    因为需要变长参数,所以在我的Signature类定义里面有一个可选的varargType参数。所以Long f(Long, List, Long…)就变成了:

    f :: Long -> List -> [Long] -> Long

    方括号表示变长。但是在我们的DSL里有这么一个函数:

    Log(String, Object, …)

    接受不限制数目的成对的(String, Object)。我的解法是给它做一下宏变换,这样Log(a,b,c,d)就变成了:

    Log(Pair(a,b), Pair(c,d))

    然后把Log()的签名写成:

    Log :: [Pair] -> void

    因为目前只有一个函数是这种奇葩的模式所以无所谓……不过如果早点看到这篇文章的话说不定varargType就变成varargTypes了。

    另外不知道typed clojure是不是允许在函数期待一个Long的时候传一个推导出来的类型是Number的表达式进去……如果可以的话可以考虑把没有被标记的函数默认标记为……

    func :: [Object] -> Object

    吧?

    • w君 says:

      我勒个擦我的排版全没了…… 好评–

    • xudifsd says:

      因为我只在豆瓣9点上推了这个文章,没在推上推,所以你就看不到啦。

      twitter内部用什么语言?自己发明了一个DSL?还是支持类型检查的?good job。。

      typed clojure是允许函数期待Number时传入Long,因为Long <: Number,反过来就不行了啊。 后面说的类似于默认为[Any * -> Any],是有这种默认。。

      • w君 says:

        ˊ_>` 记得我订阅了你的博客来着?

        twitter内部用scala和java,但是anti-spam组自己搞了一个简单粗暴表达力不强的DSL。但是对于反垃圾来说已经够用了。

        我也是允许这么做的,但是同事说好像哪里怪怪的。

        • xudifsd says:

          可能是前段时间我的博客域名过期了几天,然后你的订阅器认为他挂了。。。

Leave a Reply

Your email address will not be published. Required fields are marked *