jvm7 jvm8

目前,Java世界上最激动人心的事情之一就是正在努力使其他编程语言在虚拟机上运行。 关于JRuby,Groovy,Scala和JavaScript引擎Rhino的讨论很多。 但是为什么要停在那里? 如果您真的想跨出主流并涉足与Java完全不同的方面,那么Lisp是一个不错的选择。 而且,有几种针对JVM的Lisp编程语言的开源实现,可供探索。

那么,什么使Lisp值得一看? 一方面,这种拥有50年历史的语言一直是我们今天想当然的许多想法的催化剂。 if-then-else构造最初来自Lisp,早期尝试进行对象定向和带有垃圾回收的自动内存管理也是如此。 Lexical闭包(现在是Java程序员的热门话题)在70年代的Lisp中首次被研究。 除此之外,Lisp还具有其他语言尚未采用的许多其他独特功能,这种好主意必将在未来卷土重来。

本文针对对Lisp感到好奇的Java开发人员。 它将讨论当今JVM上可用的Lisp的不同方言,并为您提供有关Lisp编程如何工作以及使其独特之处的速成课程。 最后,我们将研究如何将Lisp代码与Java系统集成在一起。

有许多免费的和商业的Lisp系统可用于许多不同的平台。 对于想要开始探索Lisp的Java用户而言,保持JVM是一个不错的首选。 它很容易上手,并且您可以利用熟悉的所有现有Java库和工具而受益。

常见的LISP和方案

Lisp主要有两种方言:Common Lisp和Scheme。 它们基于许多相同的想法,但是仍然相差很大,引发了关于哪个是更好选择的激烈辩论。

Common Lisp是1991年最终确定的ANSI标准,合并了多个早期Lisps的思想。 这是一个为多种应用程序开发而设计的大型环境,其中最著名的是人工智能。 另一方面,Scheme出自学术界,故意更具简约性,并且已成为教学计算机科学和嵌入式脚本的好语言。 您可能会遇到的另外两个著名的Lisps是较小的特定于应用程序的DSL,例如Emacs Lisp或AutoCAD的AutoLISP。

在JVM上,有两种主要方言的实现,但是Schemes更成熟了。 Armed Bear Common Lisp( www.armedbear.org/abcl.html )是Common Lisp标准的相当完整的实现,但是它的缺点是除非您安装了另一个Common Lisp系统,否则无法构建发行版。对于初学者来说是个问题。

在方案方面,两个主要参与者是Kawa( www.gnu.org/software/kawa )和SISC( www.sisc - scheme.org-方案代码的第二个解释器)。 对于本文中的示例,我们将使用Kawa。 Kawa实际上是用于创建编译为Java字节码的新语言的框架,Scheme是其中的一种实现。 顺便说一下,它的创建者Per Bothner现在正在Sun从事JavaFX项目的编译器的工作。

另一个值得关注的竞争者是Clojure( clojure.sourceforge.net )。 这是一种新语言,它自己的Lisp方言位于Scheme和Common Lisp之间。 它是直接为JVM设计的,具有到目前为止提到的所有Lisps中最干净的Java集成,并且还具有其他一些令人兴奋的想法,例如对并发和事务内存的内置支持。 Clojure目前仍处于探索性Beta阶段,因此在其之上构建某些内容可能还为时过早,但这绝对是一个让您继续前进的项目。

读取-评估-打印循环

让我们从安装Kawa开始。 该发行版是一个jar文件,可以直接从ftp://ftp.gnu.org/pub/gnu/kawa/kawa-1.9.1.jar下载 。 一旦获得罐子,将其添加到类路径。 完成之后,您就可以通过运行以下命令来启动REPL:

java kawa.repl   #|kawa:1|# 

这将启动Kawa并提示您。 那是什么 REPL表示READ-EVAL-PRINT-LOOP,并且是与正在运行的Lisp系统进行交互的一种方式-它读取您的输入,对它们进行评估,对结果进行打印,然后再次循环回到提示。 开发Lisp程序的方式通常不同于Java编程时遵循的“编写代码,编译,运行”循环。 Lisp程序员通常会启动他们的Lisp系统并使其保持运行状态,从而使编译和运行时之间的界限模糊。 在REPL中,可以动态修改函数和变量。 代码是动态解释或编译的。

首先,真正简单的事情是:将两个数字加在一起。

#|kawa:1|# (+ 1 2)   3 

这是Lisp表达式或“形式”的典型结构。 语法非常一致:表达式始终用括号括起来,并使用前缀表示法,因此加号在两个术语之前。 要进行更高级的构造,您可以嵌套几种形式来构建树结构:

#|kawa:2|# (* (+ 1 2) (- 3 4))   -3 

Scheme的内置函数以相同的方式工作:

#|kawa:3|# (if (> (string-length "Hello world") 5)                  (display "Longer than 5 characters"))   Longer than 5 characters 

在这里,您有一个if -statement检查特定字符串是否长于五个字符。 如果碰巧是真的,例如在这种情况下,将执行下一个表达式,从而导致显示一条消息。 请注意,此处的缩进仅出于可读性目的。 如果愿意,您可以将全部内容写在一行上。

Lisp代码中用括号括起来的样式称为“ s表达式”。 它也可以用作定义结构化数据的通用方法,就像XML。 Lisp具有许多内置函数,可让您非常轻松地以s表达式格式处理数据,这反过来又成为其优势之一。 由于语法非常简单,因此编写用于生成或修改代码的程序比使用其他语言要简单得多。 当我们进入宏示例时,将会看到更多的信息。

功能

Scheme通常被认为是功能编程语言家族的一部分。 与面向对象的世界不同,这里的抽象方法主要是函数及其所操作的数据,而不是类和对象。 您在Scheme中所做的所有操作实际上都是在调用带有一些参数并返回结果的函数。 要创建函数,请使用define关键字:

#|kawa:4|# (define (add a b) (+ a b))

这定义了add ,它接受两个参数a和b。 该函数的主体只需执行+ ,并自动返回结果。 请注意,没有静态类型声明。 所有类型检查都在运行时完成,与其他动态语言相同。

使用定义的函数,您可以简单地从REPL调用它:

#|kawa:5|# (add 1 2)   3

函数是Scheme世界中的一等公民,可以像Java中的对象一样传递。 这开辟了一些非常有趣的可能性。 让我们从创建一个接受参数并将其加倍的函数开始:

#|kawa:6|# (define (double a) (* a 2))

然后通过调用list函数来定义三个数字的list

#|kawa:7|# (define numbers (list 1 2 3))

现在开始令人兴奋的部分:

#|kawa:8|# (map double numbers)   (2 4 6)

在这里,您调用了map ,它带有两个参数:另一个函数和某种列表。 map将遍历列表中的每个元素,并为每个元素调用提供的函数。 然后将结果收集到一个新列表中,这就是您可以看到返回到REPL的内容。 这是一种使用Java中的for循环解决问题的“功能性”方法。

兰伯达斯

更加方便的是使用lambda关键字定义匿名函数,类似于匿名内部类在Java中的工作方式。 重做上面的示例,您可以跳过中间double函数的定义,并将map语句编写为:

#|kawa:9|# (map (lambda (a) (* 2 a)) numbers)   (2 4 6) 

也可以定义一个仅返回lambda的函数。 一个经典的教科书示例是这样的:

#|kawa:10|# (define (make-adder a) (lambda (b) (+ a b)))   #|kawa:11|# (make-adder 2)   #

这里发生了什么? 首先,您定义了一个名为make-adder函数, a函数a一个参数amake-adder返回一个匿名函数,该函数需要另一个参数“ b”。 调用该匿名函数时,它将依次计算ab的总和。

执行(make-adder 2) -或“给我一个函数,该函数会将2加到我发送给它的参数上”-使REPL向后报告隐含的 这只是打印为字符串的实际lambda过程。 要使用此功能,您可以执行以下操作:

#|kawa:12|# (define add-3 (make-adder 3))   #|kawa:13|# (add-3 2)   5 

很棒的是,lambda可以作为一个闭包。 它“关闭”并保留对创建时作用域内变量的引用。 作为(make-adder 3)调用的结果的lambda保持a的值,当执行(add-3 2) ,它将能够计算3 + 2并返回预期的5。

到目前为止,我们所研究的功能与新型动态语言中的功能非常相似。 例如,Ruby还允许您使用匿名块来处理对象的集合,这正是我们之前使用lambda和map函数所做的。 因此,让我们现在换一下齿轮,看看更独特的Lispy:宏。

Scheme和Common Lisp都有宏系统。 当您看到人们将Lisp称为“可编程编程语言”时,这就是他们的意思。 使用宏,您实际上可以连接到编译器并重新定义语言本身。 这是Lisp统一语法真正开始奏效的地方,整个过程变得非常有趣。

举一个简单的例子,让我们看一下循环。 最初,在Scheme语言中没有定义任何循环。 迭代事物的经典方法是使用map或递归函数调用。 由于采用了称为尾部调用优化的编译器技巧,因此可以使用递归而不会耗尽堆栈。 后来引入了一个非常灵活的do命令,使用它执行循环看起来像这样:

(do ((i 0 (+ i 1)))     ((= i 5) #t)     (display "Print this ")) 

在这里,我们定义一个索引变量i ,将其初始化为零,并将其设置为每次迭代增加1。 一旦表达式(= i 5)计算为true,循环就会中断,然后返回#t(Scheme等效于Java的布尔值true)。 在循环内部,我们只打印一个字符串。

如果我们只需要一个简单的循环,那么这将是很多复杂的样板代码。 在某些情况下,最好能够做得更直接一些,例如:

(dotimes 5 (display "Print this"))

dotimes宏,可以使用适当命名的define-syntax函数为语言添加dotimes的特殊语法:

(define-syntax dotimes   (syntax-rules ()     ((dotimes count command) ; Pattern to match      (do ((i 0 (+ i 1)))     ; What it expands to          ((= i count) #t)          command)))) 

执行此操作将告诉系统,对dotimes任何调用都dotimes以特殊方式处理。 Scheme将使用我们定义的语法规则来匹配模式,并在将结果发送给编译器之前对其进行扩展。 在这种情况下,模式是(dotimes count command) ,然后将其转换为常规的do循环。

在REPL中执行此操作,您将获得:

#|kawa:14|# (dotimes 5 (display "Print this ")) Print this Print this Print this Print this Print this #t 

在此示例之后,肯定会提出两个问题。 首先,为什么我们根本需要使用宏? 这样的事情不能用常规函数完成吗? 答案是否定的-对函数的调用实际上会在其所有参数发送出去之前触发对所有参数的求值,在这种情况下这是行不通的。 例如,您将如何处理(do-times 0 (format #t "Never print this")) ? 评估需要推迟,并且只能使用宏来完成。

其次,我们在宏中使用变量i 。 如果command表达式中的代码对其变量之一恰好使用相同的名称,是否会发生冲突? 无需担心-Scheme的宏称为“卫生的”。 编译器会自动检测并知道如何解决这样的命名冲突,这对程序员是完全透明的。

既然您已经看到了这一点,请想象一下尝试将自己的循环构造添加到Java。 这几乎是不可能的。 好吧,也许不是-编译器毕竟是开源的,因此您可以自由下载它并开始使用,但这并不是一个现实的选择。 在其他动态语言中,闭包可以使您更进一步地使语言看起来像您想要的样子,但是在某些情况下,这些结构还不够强大或不够灵活,无法完全调整语法。

之所以如此,是因为Lisp经常成为当主题是元编程或领域特定语言时被击败的语言。 Lisp程序员一直是“自下而上编程”的长期拥护者,因为语言本身经过调整以适合您的问题领域,而不是相反。

从JAVA中调用计划代码

在JVM之上运行另一种语言的主要好处之一是如何将用其编写的代码与现有应用程序集成在一起。 很难想象使用Scheme对一些经常更改的复杂业务逻辑进行建模,然后将其嵌入到更稳定的Java框架中。 规则引擎Jess( www.jessrules.com )是朝着这个方向发展的一个例子,它在JVM上运行,但使用其自己的类似Lisp的语言声明规则。

但是要使不同编程语言之间的互操作性以一种干净的方式工作是一个棘手的问题,尤其是当两者之间的不匹配程度与Java和Lisp一样大时。 没有关于如何进行此集成的标准,并且JVM上存在的方言都以不同的方式处理该问题。 Kawa对Java集成有很好的支持,我们将继续使用它来研究如何使用Scheme代码定义Swing GUI。

从Java程序中运行一些Kawa代码很简单:

import java.io.InputStream;import java.io.InputStreamReader;

import kawa.standard.Scheme;

public class SwingExample {

     public void run() {

         InputStream is = getClass().getResourceAsStream("/swing-app.scm");         InputStreamReader isr = new InputStreamReader(is);

         Scheme scm = new Scheme();         try {             scm.eval(isr);         } catch (Throwable schemeException) {             schemeException.printStackTrace();         }     }

     public static void main(String arg[]) {         new SwingExample().run();     } } 

在此示例中,应该在类路径的某个位置找到包含名为swing-app.scm的Scheme程序的文件。 创建了解释程序的新实例kawa.standard.Scheme ,并调用了该实例以评估该文件的内容。

Kawa目前还不支持Java 1.6中引入的JSR-223脚本API( javax.scripting.ScriptEngine等)。 如果您需要执行此操作的Lisp,那么最好的选择是SISC。

从方案中调用JAVA图书馆

在继续编写更大的Lisp程序之前,是时候找到一个更合适的编辑器来工作了,否则跟踪所有这些括号将很快使您发疯。 最受欢迎的选择之一当然是使用Emacs-毕竟可以使用自己的Lisp方言进行编程-但是对于Java开发人员来说,坚持Eclipse可能会更舒服。 如果是这种情况,您应该在继续之前安装免费的SchemeScript插件。 可以在schemeway.sourceforge.net/schemescript.html中找到它。 还有一个用于Common Lisp开发的插件,称为Cusp( bitfauna.com/projects/cusp )。

现在,让我们看一下swing-app.scm的实际内容,以及使用Kawa定义简单GUI所需要做的工作。 本示例将打开一个带有一个按钮的小框架。 单击按钮后,将其禁用。

(define-namespace JFrame) (define-namespace JButton ) (define-namespace ActionListener ) (define-namespace ActionEvent )

 (define frame (make JFrame)) (define button (make JButton "Click only once"))

 (define action-listener   (object (ActionListener)     ((action-performed e :: ActionEvent) ::  (*:set-enabled button #f)))) 

 (*:set-default-close-operation frame (JFrame:.EXIT_ON_CLOSE))  (*:add-action-listener button action-listener)  (*:add frame button)  (*:pack frame)  (*:set-visible frame #t) 

前两行使用define-namespace命令为我们将要使用的Java类设置较短的名称,类似于Java的import语句。

然后,我们定义框架和按钮。 Java对象是使用make函数创建的。 在按钮情况下,我们将字符串作为构造函数的参数提供,而Kawa足够聪明,可以根据需要将其转换为java.lang.String对象。

现在让我们跳过ActionListener的定义,然后先看看最后五行。 这里的符号*:用来触发对象上的方法。 例如(*:add frame button)等效于frame.add(button) 。 还要注意,方法的名称是如何自动从Java的驼峰样式转换为小写的单词,并用Scheme的典型破折号分隔的。 例如,在后台将set-default-close-operation变为setDefaultCloseOperation 。 这里的另一个细节是:. 用于访问静态字段。 (JFrame:.EXIT_ON_CLOSE)JFrame.EXIT_ON_CLOSE等效。

现在回到ActionListener。 在这里, object函数用于创建实现java.awt.event.ActionListener接口的匿名类。 action-performed功能被设置为在按钮上调用setEnabled(false) 。 必须在此处添加一些类型信息,以使编译器知道应该action-performed应该是ActionListener接口中定义的void actionPerformed(ActionEvent e)的实现。 之前我们说过,在Scheme中,通常不需要类型,但是在这种情况下,当您与Java接口时,编译器需要额外的信息。

一旦拥有了这两个文件,请编译SwingExample.java并确保将生成的类和swing-app.scm放在类路径中的某个位置。 接下来,运行java SwingExample以查看GUI。 您还可以使用load函数让REPL评估文件中的代码: (load "swing-app.scm") 。 这为动态操纵GUI组件打开了一扇门。 例如,您可以通过在REPL提示符下执行(*:set-text button "New text")来切换按钮上的(*:set-text button "New text") ,然后查看更改立即生效。

当然,该示例仅用于说明如何从Kawa调用Java。 绝不是您可以想象的最优雅的Scheme代码。 如果实际上您想在Scheme中定义一个大型的Swing UI,则最好提高一点抽象级别,并将混乱的集成代码隐藏在几个精心选择的函数和宏后面。

资源

希望阅读此书可以激发对Lisp的兴趣。 当我说还有很多事情需要探索时,请相信我。 以下是一些您可以了解更多信息的资源:

  • 计算机程序的结构和解释-这本经典的计算机科学教科书是Scheme的绝佳指南( mitpress.mit.edu/sicp )
  • 实用Common Lisp-有关Common Lisp的最佳现代教程( www.gigamonkeys.com/book )
  • 击败平均水平-保罗·格雷厄姆(Paul Graham)关于Lisp作为创业公司秘密武器的有争议的文章( www.paulgraham.com/avg.html )
  • Lisp的本质-文章通过从XML和Ant到宏和特定于域的语言来解释Lisp的思想( www.defmacro.org/ramblings/lisp.html )
  • Planet Lisp和Planet Scheme-Lisp博客的聚合站点( planet.lisp.org , scheme.dk/planet )
  • schemers.org -Scheme资源的集合。
  • 外星人徽标来自lisperati.com 。

关于作者

Per Jacobsson是位于洛杉矶eHarmony.com的软件架构师。 他从事Java已有十年,并且是Lisp的业余爱好者已有两年。 您可以通过pjacobsson.com与他联系 。

翻译自: https://www.infoq.com/articles/lisp-for-jvm/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

jvm7 jvm8

jvm7 jvm8_在JVM上探索LISP相关推荐

  1. 探索JVM上的LISP

    当前Java领域最激动人心的事情莫过于可允许其它编程语言运行于Java虚拟机上.围绕JRuby.Groovy.Scala还有 Rhino(JavaScript引擎)的讨论已经甚嚣尘上.可为什么要墨守陈 ...

  2. java lisp,摸索JVM上的LISP[Java编程]

    赞助商链接 本文"摸索JVM上的LISP[Java编程]"是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其 ...

  3. jvm7 jvm8_我们真的仍然需要32位JVM吗?

    jvm7 jvm8 即使在今天(2015年),我们仍然有两个版本或Oracle HotSpot JDK –已调整为32或64位体系结构. 问题是我们是否真的想在服务器甚至笔记本电脑上使用32位JVM? ...

  4. Android单元测试(七):Robolectric,在JVM上调用安卓的类

    2019独角兽企业重金招聘Python工程师标准>>> 今天讲讲Android上做单元测试的最后一个难点,那就是在JVM上无法调用安卓相关的类,不然的话,会报类似于下的错误: jav ...

  5. JVM上的响应式流 — Reactor简介

    响应式编程 作为响应式编程方向上的第一步,微软在.NET生态系统中创建了Rx库(Reactive Extensions).RxJava是在JVM上对它的实现. 响应式编程是一个异步编程范式,通常出现在 ...

  6. restful和rest_HATEOAS的RESTful服务:JVM上的REST API和超媒体

    restful和rest 1.简介 到目前为止,我们已经花了很多时间谈论了相当数量的关于角色的的超媒体和HATEOAS在REST风格的 Web服务和API,扫视不同规格和可用性方面. 听起来好像支持超 ...

  7. jvm高并发_在JVM上对高并发HTTP服务器进行基准测试

    jvm高并发 在第一篇关于HTTP客户端的文章 (我将您重定向到JVM上的高效HTTP的介绍)之后,现在让我们来谈谈HTTP 服务器 . 有一些关于HTTP服务器的基准测试,但通常受到诸如以下缺点的阻 ...

  8. jvm上的随机数与熵_向您的JVM添加一些熵

    jvm上的随机数与熵 能否生成真正的随机数取决于系统中的熵. 有人声称,这可以通过掷骰子来保证. 其他人认为,用此主体替换OpenJDK的java.math.Random.nextInt()方法将有所 ...

  9. javaone_JavaOne 2012:在JVM上诊断应用程序

    javaone 值得参加Staffan Larsen (Oracle Java Serviceability Architect)的演讲" 在JVM上诊断您的应用程序 "(Hilt ...

最新文章

  1. [JS] [编程题] 配置文件恢复
  2. 编程实现灰度处理函数
  3. 强化学习Exploration漫游
  4. JZOJ__Day 6:【普及模拟】Oliver的成绩(score)
  5. 开发DBA(APPLICATION DBA)的重要性
  6. 阿里达摩院python_阿里达摩院出品的735集的python教程
  7. Eclipse中activiti插件的安装
  8. jupyter notebook使用opencv的例子_VSCode中使用jupyter notebook
  9. A*寻路算法的探寻与改良(一)
  10. 基于Metronic的Bootstrap开发框架经验总结(15)-- 更新使用Metronic 4.75版本
  11. Erros while compiiing.Reload prevented
  12. flightgear通过UDP协议输出飞行态势数据以及利用C++接收flightgear数据
  13. 【061】百度迁徙-用地图大数据演绎国人的迁徙史诗
  14. 高通平台抓取ramdump并用qcap解析
  15. 用JAVA爬虫爬网站的图片
  16. 教你自己训练的pytorch模型转caffe(二)
  17. C语言HeapBottomUP算法,C语言堆的建立Percolate Up and Down
  18. 曾国藩:统领30万湘军,只靠这4句话
  19. 一种简明易懂的专利侵权分析报告表单样式
  20. RISC-V技术杂谈

热门文章

  1. 自相关系数与偏自相关系数,拖尾与截尾
  2. PCB电路板瑕疵检测图像数据集(600多张,含标签)
  3. Thinkpad T470 拆装字母键帽支架
  4. 张悟本教授----不吃药的食疗医病方案(一)
  5. 那个「最牛删库跑路事件」的程序员,被判了....
  6. C语言实现计算器+-*/连加运算
  7. 添加数据+二级联动+全选删除
  8. Django url末尾斜杆 / 的重定向问题
  9. 驾考宝典2014电脑版 v5.1.3 pc版
  10. 互联网摸鱼日报(2023-06-21)