在Vert.x上实现Clojure语言支持的探索
Posted Vertx北京用户组
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Vert.x上实现Clojure语言支持的探索相关的知识,希望对你有一定的参考价值。
目标:让用户能够在Vert.x上用Clojure语言完成开发
条件:使用者不使用Java等其它语言
先说好,要实现这个目标的路还比较长,目前仅仅是刚开始,路嘛,走的人多了,也就成了路,千里之行,始于足下,本文目前只是一个探索式的讨论,还远没有达到生产和应用级别,只是先做一个可行性分析,目的仅仅是把最近的探索找个地方留下个脚印,以备将来不时之需
我们知道,Vert.x是一款支持多语言的工具集,本身提供了一个实现了类似于Actor Model的一套框架,在该框架下,用户只需要实现Verticle组件然后将其部署,便可使用,是不是很方便呢?那在Vert.x上,目前已经实现了7种语言的支持,大致可以分为脚本和对象(class)层面支持两类,在脚本中,一个文件便是一个Verticle,而在面向对象语言中,一个Verticle是一个Class,需要用户显式声明并继承实现该Class。脚本是面向过程的编程范式(pp),也就是脚本语言在书写的时候,顶层/top/1st level结构不考虑封装,所以一个脚本文件就是一个过程式的执行过程,而面向对象语言则要求在顶层封装成类/class,比如Java就是典型,而在面向对象编程范式(oop)上添加了函数式编程范式(fp)的语言,例如Scala, Kotlin,则要求在顶层封装成类或者方法,在万事万物都是对象这一理念看来,方法本身也是一个类,比如Java里面的Function class,所以在这一点上并不冲突,所以Scala, Kotlin等虽然带有FP属性,但是并不妨碍它们是比Java更加纯粹的面向对象语言。在这里可以看到,当前Vert.x还缺少纯粹的函数式编程范式语言的支持,JVM上Haskell(eta-lang)已经将Vert.x支持列入了eta0.2计划中,而fp另一大分支lisp的支持曾经在Vert.x v2时代出现过,但是在v3中目前还没有实现,那既然如此,我们不妨就踩出这第一步,短时间内肯定做不完,但是没有关系,千里之行,始于足下,凡事总要有个开始嘛。
先来看fp语言跟oop语言的主要差异点,如果我们从Java的角度出发,那有没有可能在Java里面实现一个fp编程范式呢?嗯,也不是完全不可能,首先先把Java变成过程式编程,我们知道Java的入口是一个静态方法,public static void main,那如果我们把Java整个过程写成各种static成员的话,那就是面向过程式编程,一个main方法从头写到尾,所有的东西都在这个方法里,这样做的问题就是不利于代码的复用,所以我们要把一些重复使用的东西从里面抽出来,做成可复用的部分,比如单独写到另外一个文件中去,那oop的哲学就是将这些可以复用的部分全部做成类,认为万事万物都是名词,这些可以复用的代码也都是名词化的抽象,这比较符合我们所在物理世界的逻辑。但是函数式编程范式则没有这个要求,函数式编程则讲究将函数抽象出来,做成纯粹的输入&输出,这样函数就可以复用啦,除了纯粹的面向对象语言,其它编程范式不要求把所有代码全部封装到类中去,但是函数式编程一样需要我们做封装,不封装就没法复用,只不过封装的都是函数而已,那在Java里面,要实现这一点,那就是静态方法,如果我们把所有的class全部做成静态方法的包装,那就跟fp里面的module很接近了,在Clojure里面称之为namespace,命名空间。
这就带出了Clojure和Java最大的不同,因为Clojure里面所有的方法和变量在Java看来都是静态的,所以就没有办法实例化Clojure的命名空间,也就是说Verticle这个控件对象不存在,那这个时候该肿么办?我们初步的打算是保留Actor Model,先用Java实现Verticle,然后通过调用命名空间中的方法,来实现其业务逻辑。
说干就干,工欲善其事,必先利其器,如果我们要在生产实践中使用该语言,有两个东西是必需要有的:
IDE的支持,最好是免费的
编译器,让我们能够执行我们写好的源代码
当然Clojure官方有提供REPL和lein,但是作为长期被Java eco毒害的程序员来说,我还是更喜欢IDEA和maven, gradle这些,幸运的是,这两个我们都有初步的解决方案啦。
先说第一个,我们希望在IDEA中高亮显示我们的clj源文件,并做一些自动补全之类的操作,这里我们推荐使用Clojure-Kit插件,免费的,但是注意,这不是JetBrains官方的插件,所以别找错仓库啦。下载后安装后重启,然后新建一个test.clj文件,就可以看到可爱的太极图标啦,右边是高亮显示的效果,如下图:
效果还不错吧?
然后第二个,我们暂时不考虑将clj文件编译成字节码这回事,因为clj可以编译执行,效果类似于脚本的解释执行,所以我们可以将clj文件直接打入jar包中使用,当然也可以编译成.class,这一点上Clojure和Groovy很像,都是既可以编译也可以不编译的语言。如果我们不编译直接编译执行源文件的话,我们需要clj的官方jar包,在maven仓库上可以很轻松找到,这个就是我们需要的依赖:org.clojure " clojure
最后是如何实现在Verticle中调用我们的写好的Clojure代码,这里我们先写一个clj文件,在该文件中,我们实现了几个纯函数,其中最关键的一个是start方法,因为我们需要保证函数的纯洁性,而且Clojure似乎还没有对象内范围这回事,所以如果我们用def声明一个变量,这个变量会变成全局的,类似Java的static变量,可以将该变量绑定到threadlocal上去,但是这似乎也还是不能满足我们的要求,因为Verticle是一个对象,而不是一个线程,一个线程会对应多个Verticle对象。所以最简单的方式就是将Verticle中的start方法做成一个纯函数,因为Vert.x在部署Verticle的时候会执行一边start方法,所以我们需要做的就是做一层小小的包装,让start方法调用clj中的start方法便可,因为在Verticle中,执行start方法时会有两个副作用,分别是vertx和context内置对象,为了保证我们能在clj的start方法中获取到这两个对象,我们需要将这两个对象作为参数传入该start方法,这是实现代码:
public void start() {
//部署部分
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("com.whitewoodcity.test"));
IFn startIFn = Clojure.var("com.whitewoodcity.test","get-start-inf");
System.out.println(startIFn.invoke());
IFn inc = Clojure.var("com.whitewoodcity.test", "start");
inc.invoke(vertx, context);}
对应的test.clj
(ns com.whitewoodcity.test)(import
io.vertx.core.Vertx)(import
io.vertx.core.Context)(import
io.vertx.core.eventbus.Message)(defn arities [v]
(-> v meta :arglists))(defn handler [f]
(reify
io.vertx.core.Handler
(handle [this message]
(f message))))(defn start [^Vertx vertx ^Context context]
(.consumer (.eventBus vertx) "com.whitewoodcity.test"
(handler (fn [^Message message] (. message reply "got it")))))(defn get-start-inf [] (arities #'start))
本文旨在探索实现Vert.x Clojure语言支持的可能性,所以很多是测试和实现代码,实现代码会有Java代码出现,如果将来完成该语言支持,那么用户是不需要使用Java代码便可编写出Vert.x应用来滴。
在我们实现代码的时候,可能需要使用到ns和函数自身的一些元数据,例如方法是什么名字,参数是什么等,因为不是所有的部署都需要用到context这个内置对象,所以实现之后可能会同时支持start[],start[^Vertx vertx]和start[^Vertx vertx ^Context context]这几个方法,到底是哪个就需要分析函数本身的参数了,所以我实现了一个arities方法以获取相关参数,这里的关键字:arglists表示是参数列表,通过get-start-inf方法可以将信息读出,作为String返回给Java代码。
vertx.eventBus().send("com.whitewoodcity.test","this is a message",ar->{
System.out.println(ar.result().body());
});
这里还有一个小小的槛,就是Clojure中的lambda,并不是Java中的lambda,所以在调用Vert.x api的时候,callback无法直接用Clojure的lambda填入参数,需要使用reify函数将其实现,这里我实现了一个高阶函数handler,该函数接收一个函数作为参数,然后返回一个实现了io.vertx.core.Handler接口的函数,使用时候就多了一层括号,例如:
(handler #(println "test"))
最后在Clojure中使用Java api,可以通过^Vertx也就是用^开头加上类名的方式声明变量,这样Clojure-Kit就会给你自动提示啦,是不是很方便呢?记得import,这样开发就方便很多啦,比起自己在vim里面裸写是不是要简单不少呢?
todo:
这里介绍了如何实现一个简单的Vert.x框架予以支持Clojure语言开发,后续的打算是先通过Vert.x的codegen生成clj风格的api,例如vertx.event-bus, vertx.create-http-client而不是vertx.eventBus, vertx.createHttpClient,然后再通过实现通用的namespace和ClojureVerticle以提供一个Vert.x上支持Clojure开发的环境。
嗯,大概就这些,目前这一切都还只是一种可行性的探索,离真正投产还有很长的距离,Vert.x是一个多语言支持的环境,呼吁有识之士贡献开源
欢迎关注赵总的知乎专栏:https://zhuanlan.zhihu.com/whitewoodcity
转载请注明作者和出处
往期精彩内容回看:
回复关键词“消息应用”——看Vertx玩转基于消息的应用
回复关键词“微服务”——从零基础迈向微服务之巅
回复关键词“Web初探”——Vertx-Web初探
回复关键词“次时代”——次时代Java编程
回复关键词“认证”——基于Auth Common开发自定义认证组件
回复关键词“游戏服”——Vert.x分布式游戏服实战心得
以上是关于在Vert.x上实现Clojure语言支持的探索的主要内容,如果未能解决你的问题,请参考以下文章