clojure语言_Clojure编程语言

clojure语言

本文介绍了Clojure编程语言。 Clojure是Lisp的方言。 假定您还不了解Lisp。 而是假定您具有Java技术知识。 要编写Clojure程序,您需要Java Development Kit V5或更高版本以及Clojure库。 对于本文,使用了JDK V1.6.0_13和Clojure V1。 您还应该利用Eclipse的Clojure插件(clojure-dev),并且将需要Eclipse。 在本文中,使用了Eclipse V3.5以及clojure-dev 0.0.34。 请参阅相关主题的链接。

什么是Clojure?

不久之前,在Java虚拟机(JVM)上运行程序意味着使用Java编程语言编写程序。 那些日子已经一去不复返了,因为现在您有很多选择。 许多流行的选择,例如Groovy,Ruby(通过JRuby)和Python(通过Jython),都允许使用更具过程性,脚本化的编程风格,或者它们具有自己的面向对象编程风格。 这些都是Java程序员熟悉的范例。 有人可能会争辩说,使用这些语言,您编写的程序与使用Java语言编写的程序相似。 您只需要使用其他语法即可。

Clojure是JVM的另一种编程语言。 但是,它与Java技术或提到的任何其他JVM语言完全不同。 它是Lisp的方言。 Lisp编程语言家族已有很长的历史了-实际上自1950年代以来。 Lisp使用不同的S表达式或前缀表示法。 这种表示法可以概括为(function arguments...) 。 您始终以函数名称开头,并列出零个或多个参数以传递给该函数。 通过使用括号将函数及其参数组合在一起。 这导致Lisp的商标之一:很多括号。

您可能会猜到,Clojure是一种功能编程语言。 学术界可以争论它的“纯度”,但是它绝对包含了函数式编程的Struts:避免可变状态,递归,高阶函数等。Clojure还是一种动态类型化的语言,尽管您可以选择添加类型信息来提高性能。代码中的关键路径。 Clojure不仅在JVM上运行,而且在设计时考虑了Java的互操作性。 最后,Clojure是一种在设计时考虑了并发性的语言,并且具有与并发编程相关的一些独特功能。

通过示例Clojure

对于许多人来说,学习新语言的最佳方法是开始编写代码。 本着这种精神,我们将处理一些简单的编程问题,并使用Clojure解决它们。 我们将详细介绍解决方案,以更好地了解Clojure的工作原理,使用方式以及它做得如何。 但是,像其他任何语言一样,我们需要建立一个开发环境来使用它。 幸运的是,使用Clojure可以很容易地做到这一点。

最少的设置

使用Clojure所需的全部是JDK和Clojure库,它是单个JAR文件。 开发和运行Clojure程序有两种常用方法。 最常见的是使用其read-eval-print-loop(REPL)。

清单1. Clojure REPL
$ java -cp clojure-1.0.0.jar clojure.lang.Repl
Clojure 1.0.0-
user=>

该命令是从Clojure JAR所在的目录运行的。 根据需要调整JAR的路径。 您还可以创建脚本并执行脚本。 为此,您需要执行一个名为clojure.main的Java类。

清单2. Clojure主要
$ java -cp clojure-1.0.0.jar clojure.main /some/path/to/Euler1.clj 
233168

同样,您需要调整Clojure JAR和脚本的路径。 最后,IDE支持Clojure。 Eclipse用户可以使用其Eclipse更新站点安装clojure-dev插件。 安装完成后,确保您在Java透视图中,然后可以创建一个新的Clojure项目和新的Clojure文件,如下所示。

图1.使用clojure-dev,Eclipse的Clojure插件
使用clojure-dev,Eclipse的Clojure插件

使用clojure-dev,您可以突出显示一些基本的语法,包括括号匹配(任何Lisp都必须具备)。 您还可以在直接嵌入Eclipse的REPL中启动任何脚本。 截至本文撰写时,该插件仍是一个非常新的插件,其功能正在Swift发展。 现在我们已经有了基本的设置,让我们通过编写一些Clojure程序来探索语言。

示例1:使用序列

Lisp的名称来自“列表处理”,通常说Lisp中的所有内容都是一个列表。 在Clojure中,这被概括为序列。 对于第一个示例,我们将处理以下编程问题。

如果我们列出所有低于10的自然数,这些自然数是3或5的倍数,则得到3、5、6和9。这些倍数的总和为23。找到1,000以下的3或5的所有倍数的总和。

这个问题来自于Euler项目,它是一组数学问题,可以使用聪明的(或有时不太聪明的)计算机编程来解决。 实际上,这是问题1。清单3显示了使用Clojure的解决方案。

清单3.来自项目Euler的示例1
(defn divisible-by-3-or-5? [num] (or (== (mod num 3) 0)(== (mod num 5) 0))) 

(println (reduce + (filter divisible-by-3-or-5? (range 1000))))

第一行定义一个函数。 请记住:函数是Clojure中程序的主要构建块。 大多数Java程序员习惯于将对象作为其程序的构建块,因此使用函数可能需要一些时间来适应。 您可能会认为defn是该语言的关键字,但实际上它是一个宏。 宏允许您扩展Clojure编译器,以从本质上向该语言添加新的关键字。 因此, defn并不是语言规范的一部分,而是由语言的核心库添加的。

在这种情况下,它正在创建一个称为divisible-by-3-or-5?的函数divisible-by-3-or-5? 。 这遵循Clojure命名约定。 单词之间用连字符隔开,函数名称以问号结尾,表明它是谓词,它返回true或false。 该函数采用一个名为num参数。 如果有更多输入参数,它们将出现在方括号内,并用空格分隔。

接下来是函数的主体。 首先,我们调用or函数。 这是正常的逻辑or习惯; 它只是一个函数,而不是一个运算符。 我们将其传递给参数。 这些也是表达式。 第一个表达式以==函数开头。 这会对传递给它的参数进行基于值的比较。 有两个参数传递给它。 首先是另一种表达方式; 此表达式调用mod函数。 这是数学的模运算符,或者是Java语言中的%运算符。 它返回余数,因此,在这种情况下,将num除以3后得到余数。将该余数与0进行比较(它是余数0,因此num被3整除)。 同样,我们检查num除以5后的余数是0。如果这些余数之一为0,则函数返回true。

在下一行,我们将创建一个表达式并将其打印出来。 让我们从最里面的括号开始。 在这里,我们调用范围函数并传入数字1000。 这将创建一个从0开始的所有小于1000的序列。 这正是我们要检查的数字集,以查看它们是否可以被3或5整除。移出后,我们调用filter函数。 这有两个参数:第一个是另一个函数,必须是谓词,因为它必须返回true或false; 第二个参数是一个序列-在这种情况下为序列(0, 1, 2, ... 999)filter函数应用谓词,如果返回true,则序列中的元素将添加到结果中。 谓词只是divisible-by-3-or-5? 在上面的行中定义的功能。

因此,过滤器表达式将产生一个整数序列,每个整数均小于1,000,并且可以被3或5整除。这正是我们感兴趣的整数集合,因此现在我们只需要添加它们即可。 为此,我们使用reduce函数。 该函数有两个参数:一个函数和一个序列。 它将功能应用到序列中的前两个元素。 然后,它将函数应用于序列中的上一个结果和下一个元素。 在这种情况下,函数是+函数或加法。 因此,它将添加序列中的所有元素。

看一下清单3,少量代码中发生了很多事情。 这是Clojure的吸引力之一。 发生了很多事情,但是一旦您习惯了这种记法,代码就变得不言自明了。 当然,要做同样的事情将需要更多的Java代码。 让我们继续另一个例子。

示例2:懒惰是一种美德

对于此示例,我们将研究Clojure中的递归和惰性。 对于许多Java程序员来说,这是另一个新概念。 Clojure允许您定义“惰性”序列,因为直到需要它们时才计算它们的元素。 这允许您定义无限序列,并且您肯定看不到Java语言中的序列。 要查看一个何时特别有用的示例,让我们看一个涉及函数式编程另一个重要方面的示例:递归。 再次,我们使用来自Euler项目的编程问题,但这一次是问题2。

斐波那契数列中的每个新项都是通过将前两个项相加而生成的。 以1和2开头,前10个项将是:1、2、3、5、8、13、21、34、55、89,...

找出序列中所有不超过400万的偶数项之和。 为了解决这个问题,Java程序员可能会想定义一个给您第n个斐波那契数的函数。 一个简单的实现如下所示。

清单4.天真的斐波那契函数
(defn fib [n] 
    (if (= n 0) 0
        (if (= n 1) 1
            (+ (fib (- n 1)) (fib (- n 2))))))

这检查n是否为0;否则为0。 如果是,则返回0。然后检查n是否为1。如果是,则返回1。否则,计算第(n-1)个斐波那契数和第(n-2)个斐波那契数并将它们加在一起。 这当然是正确的,但是如果您做了很多Java编程,就会发现问题所在。 这样的递归定义将Swift填充堆栈并导致堆栈溢出。 斐波那契数形成一个无限序列,因此应使用Clojure的无限惰性序列进行描述。 这是清单5.注意,虽然Clojure的具有更高效的斐波那契实现,它是标准库(Clojure的-的contrib)的一部分,它是更为复杂所示,所以这里显示的斐波那契序列来自斯图尔特哈洛韦的书(见相关主题想要查询更多的信息)。

清单5.斐波那契数的惰性序列
(defn lazy-seq-fibo 
    ([] 
        (concat [0 1] (lazy-seq-fibo 0 1))) 
    ([a b] 
        (let [n (+ a b)] 
            (lazy-seq 
                (cons n (lazy-seq-fibo b n))))))

在清单5中, lazy-seq-fibo函数具有两个定义。 第一个定义没有参数,因此为空方括号。 第二个定义有两个参数[ab] 。 对于无参数的情况,我们采用序列[0 1]并将其连接到一个表达式。 该表达式是对lazy-seq-fibo的递归调用,但是这一次,它正在调用两个参数的情况,将0和1传递给它。

两个参数的情况以let表达式开始。 这是Clojure中的变量分配。 表达式[n (+ ab)]定义变量n并将其设置为等于a+b 。 然后,它使用了lazy-seq宏。 顾名思义, lazy-seq宏用于创建惰性序列。 它的身体是一种表达。 在这种情况下,它使用了cons函数。 这是Lisp中的经典功能。 它接受一个元素和一个序列,并通过在该元素之前添加一个新序列来返回该序列。 在这种情况下,该序列是再次调用lazy-seq-fibo函数的结果。 如果此序列不是惰性的,就会一次又一次调用lazy-seq-fibo函数。 但是, lazy-seq宏确保仅在访问元素时才调用该函数。 要查看此序列的实际效果,可以使用REPL,如清单6所示。

清单6.生成斐波那契数
1:1 user=> (defn lazy-seq-fibo 
    ([] 
        (concat [0 1] (lazy-seq-fibo 0 1))) 
    ([a b] 
        (let [n (+ a b)] 
            (lazy-seq 
                (cons n (lazy-seq-fibo b n))))))
#'user/lazy-seq-fibo
1:8 user=> (take 10 (lazy-seq-fibo))
(0 1 1 2 3 5 8 13 21 34)

take函数用于从序列中获取一定数量(在这种情况下为10个)元素。 现在我们有了生成斐波那契数的好方法,让我们解决这个问题。

清单7.示例2
(defn less-than-four-million? [n] (< n 4000000))

(println (reduce + 
    (filter even? 
        (take-while less-than-four-million? (lazy-seq-fibo)))))

在清单7中,我们定义了一个函数,该函数less-than-four-million? 。 这只是测试其输入是否少于400万。 在下一个表达式中,从最内部的表达式开始很有用。 我们首先得到无限斐波那契数列。 然后,我们使用take-while功能。 这就像take函数,但是需要一个谓词。 一旦谓词返回false,它将停止从序列中提取。 因此,在这种情况下,只要我们得到的斐波那契数大于400万,我们就会停止采用。 我们得到这个结果并应用一个过滤器。 过滤器使用内置的even? 功能。 该函数可以实现您的预​​期:测试数字是否为偶数。 结果是所有的斐波那契数小于400万甚至更少。 现在,我们像第一个示例一样使用reduce对其求和。

清单7解决了手头的问题,但并不完全令人满意。 要使用take-while函数,我们必须定义一个非常简单的函数,称为less-than-four-million? 。 事实证明,这不是必需的。 Clojure支持闭包也就不足为奇了。 这样可以简化清单8中的代码。

Clojure中的闭包

闭包在许多编程语言中都很常见,尤其是在函数语言(例如Clojure)中。 函数不仅是一等公民,而且可以作为参数传递给其他函数,而且可以内联或匿名定义。 清单8使用闭包显示了清单7的简化形式。

清单8.更简单的解决方案
(println (reduce + 
    (filter even? 
        (take-while (fn [n] (< n 4000000)) (lazy-seq-fibo)))))

在清单8中,我们使用了fn宏。 这将创建一个匿名函数并返回它。 谓词函数通常非常简单,最好使用闭包进行定义。 事实证明,Clojure具有一种更简化的定义闭包的方式。

清单9.速记闭合
(println (reduce + 
    (filter even? 
        (take-while #(< % 4000000) (lazy-seq-fibo)))))

我们使用#来创建闭包,而不是fn宏。 我们还对传递给函数的第一个参数使用了%符号。 如果函数接受多个参数,则也可以将%1用作第一个参数,类似地使用%2%3等。

仅通过这两个简单的示例,我们就看到了Clojure的许多功能。 Clojure的另一个重要方面是它与Java语言的紧密集成。 让我们看另一个示例,其中利用Clojure的Java会有所帮助。

示例3:使用Java技术

Java平台可以提供很多东西。 JVM的性能以及核心API的丰富性以及用Java语言编写的大量第三方库都是强大的工具,可以使您免于重造过多的负担。 Clojure围绕这些想法而构建。 调用Java方法,创建Java对象,实现Java接口以及扩展Java类很容易。 要查看一些示例,让我们看一下另一个Euler项目问题​​。

清单10. Euler项目的第8个问题
Find the greatest product of five consecutive digits in the 1000-digit number.

73167176531330624919225119674426574742355349194934 
96983520312774506326239578318016984801869478851843 
85861560789112949495459501737958331952853208805511 
12540698747158523863050715693290963295227443043557 
66896648950445244523161731856403098711121722383113 
62229893423380308135336276614282806444486645238749 
30358907296290491560440772390713810515859307960866 
70172427121883998797908792274921901699720888093776 
65727333001053367881220235421809751254540594752243 
52584907711670556013604839586446706324415722155397 
53697817977846174064955149290862569321978468622482 
83972241375657056057490261407972968652414535100474 
82166370484403199890008895243450658541227588666881 
16427171479924442928230863465674813919123162824586 
17866458359124566529476545682848912883142607690042 
24219022671055626321111109370544217506941658960408 
07198403850962455444362981230987879927244284909188 
84580156166097919133875499200524063689912560717606 
05886116467109405077541002256983155200055935729725 
71636269561882670428252483600823257530420752963450

在这个问题中,我们有一个1000位数字。 这可以使用BigInteger在Java技术中用数字表示。 但是,我们不需要对整数进行计算-一次只计算五位数字。 因此,将其视为字符串更容易。 但是,要进行计算,我们需要将数字视为整数。 幸运的是,有Java语言的API,可以在字符串和整数之间来回切换。 首先,我们需要从上方处理大量不合规定的文字。

清单11.解析文本
(def big-num-str 
    (str "73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450"))

在这里,我们利用Clojure对多行字符串的支持。 我们使用str函数来解析多行字符串文字。 然后,我们使用def宏定义一个名为big-num-str的常量。 但是,将其转换为整数序列最有用。 这在清单12中完成。

清单12.创建一个数字序列
(def the-digits
    (map #(Integer. (str %)) 
        (filter #(Character/isDigit %) (seq big-num-str))))

再次,让我们从最里面的表达式开始。 我们使用seq函数将big-num-str变成一个序列。 但是,事实证明,此顺序并非我们想要的。 您可以在REPL的帮助下看到它,如下所示。

清单13.检查big-num-str序列
user=> (seq big-num-str)
(\7 \3 \1 \6 \7 \1 \7 \6 \5 \3 \1 \3 \3 \0 \6 \2 \4 \9 \1 \9 \2 \2 \5 \1 \1 \9
 \6 \7 \4 \4 \2 \6 \5 \7 \4 \7 \4 \2 \3 \5 \5 \3 \4 \9 \1 \9 \4 \9 \3 \4
 \newline...

REPL将字符(Java字符)显示为\c 。 因此\7是char 7,而\newline是char \ n( \newline行)。 这就是我们直接解析文本所得到的。 显然,在进行任何有用的计算之前,我们需要摆脱换行符并转换为整数。 清单11中就是这样做的。在这里,我们使用了一个过滤器来删除换行符。 再次注意,对于传递给filter函数的谓词函数,我们使用了简写形式的闭合。 闭包使用Character/isDigit 。 这是java.lang.Character的静态方法isDigit 。 因此,过滤器仅允许输入数字字符,放弃换行符。

现在我们已经摆脱了换行符,因此我们需要转换为整数。 在清单12中由内向外移动,请注意,我们使用map函数,该函数具有两个参数:函数和序列。 它返回一个新序列,其中序列的第n个元素是将该函数应用于原始序列的第n个元素的结果。 对于函数,我们再次使用简写的闭合符号。 首先,我们使用Clojure中的str函数将char转换为字符串。 我们为什么要做这个? 因为接下来,我们使用java.lang.Integer的构造函数创建一个整数。 这由Integer表示。 您可以将此表达式视为新的java.lang.Integer(str(%)) 。 将其与map函数配合使用,就可以得到所需的整数序列。 现在我们可以解决问题了。

清单14.示例3
(println (apply max 
    (map #(reduce * %)
        (for [idx (range (count the-digits))] 
            (take 5 (drop idx the-digits))))))

为了理解这段代码,让我们从for宏开始。 这不像Java语言中的for循环。 相反,它是一个序列理解。 首先,我们使用方括号创建绑定。 在这种情况下,我们将变量idx绑定到一个从0 ... N-1的序列,其中N是the-digits序列中the-digits元素the-digits (N = 1,000,因为原始数字有1,000位)。 接下来, for宏采用一个表达式来生成新序列。 它将遍历idx序列的每个元素,评估表达式,并将结果添加到返回序列中。 您可以看到它在某些方面如何像for循环。 理解中使用的表达式将首先使用drop函数删除序列的前M个元素,然后使用take函数获取缩短序列的前五个元素。 请记住, M将为0,然后为1,然后为2,依此类推,因此结果将是一系列序列,其中第一个元素为(e1,e2,e3,e4,e5),下一个元素为( e2,e3,e4,e5,e6)等,其中e1,e2等是the-digits中的元素。

现在我们有了这个序列序列,我们使用map函数。 通过使用reduce函数,我们将五个数字的每个序列转换为这五个数字的乘积。 现在我们有一个整数序列,其中第一个元素是元素1-5的乘积,第二个元素是元素2-6的乘积,依此类推。我们想要最大的此类乘积。 为此,我们使用max函数。 但是, max希望将多个元素传递给它,而不是单个序列。 要将序列转换为多个元素以传递给max ,我们使用apply函数。 这产生了我们想要解决的最大问题,当然可以打印出答案。 现在您已经解决了几个问题,同时学习了如何使用Clojure。

摘要

在本文中,我们介绍了Clojure编程语言,并受益于使用Eclipse的Clojure插件。 我们简要介绍了它的一些原理和功能,但仅关注代码示例。 在这些简单的示例中,我们已经看到了该语言的许多核心功能:函数,宏,绑定,递归,惰性序列,闭包,理解以及与Java技术的集成。 Clojure还有许多其他方面。 希望该语言引起您的注意,并且您将看一看一些资源并对其进行更多了解。


翻译自: https://www.ibm.com/developerworks/opensource/library/os-eclipse-clojure/index.html

clojure语言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值