閱讀758 返回首頁    go 技術社區[雲棲]


Clojure快餐教程(1) - 運行在JVM上的Lisp方言

Clojure快餐教程(1) - 運行在JVM上的Lisp方言

Java作為目前為止被使用最廣泛的使用虛擬機的編程語言,帶動了JVM上語言族的繁榮。
有根紅苗正的為JVM設計的動態語言Groovy,目前最主要被用於Gradle編譯環境中;也有Jython, JRuby等動態語言在JVM上的實現,也有scala這樣強大的混合語言。
在這之中,clojure是比較特殊的一種,它是Lisp語言在JVM上的一種方言。

使用clojure調用java

首先我們先看一下如何用clojure來調用java的方法。
有了這個利器之後,我們就獲得了整個java世界的類庫的強大支持。

首先我們看個例子,做個最簡單的兩個大整數的乘法。用Java寫是這樣風格的:

import java.math.BigInteger;

public class Test {
    public static void test(){
        BigInteger bi1 = new BigInteger("1234567890");
        BigInteger bi2 = new BigInteger("9876543210");
        System.out.println(bi1.multiply(bi2));
    }
}

用Clojure寫起來,一開始可能會有點不適應,會是這樣的:

(ns .test2)
(import java.math.BigInteger)

(println (.multiply (new BigInteger "1234567890") (new BigInteger "9876543210")))

這裏可能會給代碼補全帶來一點困難,因為在寫.multiply時,還不知道它是應用在哪個對象或者類上。但是Clojure就是這種風格了,函數名、運算符先行。

Clojure對於Lisp的改進

Clojure作為運行在JVM上的Lisp方言,第一個優勢就是可以無縫調用Java API。另外,針對Lisp括號多的問題,Clojure除了S表達式中常用的()之外,增加了[]{}兩種括號。

()表示列表,[]表示向量,{}用於表示哈希表。
另外,#{}用來表示集合。

user=> (class [])
clojure.lang.PersistentVector
user=> (class ())
clojure.lang.PersistentList$EmptyList
user=> (class {})
clojure.lang.PersistentArrayMap
user=> (class #{})
clojure.lang.PersistentHashSet

在Clojure中生存

查看幫助 - clojure.repl/doc

doc宏用於查看常量、特殊表、函數或者宏的文檔。在以後的編程中我們會經常使用它。

例:

user=> (doc doc)
-------------------------
clojure.repl/doc
([name])
Macro
  Prints documentation for a var or special form given its name

列表

在Lisp中,列表數據和代碼的表示形式一樣,都是S表達式。S表達式可以通俗理解為一個括號括起來的,以第一個符號進行求值的表達式。
所以,如果要避免一個列表被求值,我們有兩種辦法:

  1. 用list函數來生成列表
  2. 用quota特殊表來避免被求值

例:

user=> (list 1 2 3)
(1 2 3)
user=> (doc list)
-------------------------
clojure.core/list
([& items])
  Creates a new list containing the items.
nil
user=> (quote (2 3 4))
(2 3 4)
user=> (doc quote)
-------------------------
quote
  (quote form)
Special Form
  Yields the unevaluated form.

  Please see https://clojure.org/special_forms#quote
nil
user=> '(3 4 5)
(3 4 5)

"'"符號是quote的簡寫形式。

列表操作

lisp中最常用的car和cdr在clojure中不被支持,當然更不用想caddr之類的了。

如果要取表頭,需要使用first函數,例:

user=> (first (list 2 3 4))
2

相對地,取除了表頭之外的部分,使用rest函數,例:

user=> (rest (list 2 3 4))
(3 4)

如果取最後一個元素,可以使用last函數獲取最後一個元素,例:

user=> (last '(5 6 7))
7

一般地,我們如果想取第n個下標的元素的話,可以使用nth函數:

user=> (nth (list 8 9 10) 2)
10

雖然沒有car和cdr,但是將fist和rest連接在一起的cons函數還是有的:

user=> (cons 1 '(2 3 4))
(1 2 3 4)

向量

向量有點類似於數組,對於隨機訪問元素進行優化。clojure使用中括號來表示向量。
由於不是用的S表達式,所以也就不用quote了。
first, last, nth和rest函數對於向量仍然適用。

user=> (first [1 2 3 4])
1
user=> (nth [4 5 6 7] 3)
7
user=> (rest [3 4 5 6])
(4 5 6)

但是請注意rest返回的並不是一個向量,而且一個序列類型。
我們可以用class函數來看一下具體的類型:

user=> (class (rest '(1 2 3)))
clojure.lang.PersistentList
user=> (class (rest [1 2 3]))
clojure.lang.PersistentVector$ChunkedSeq

哈希表

用完了小括號和中括號,下麵是大括號出場的時候了。沒錯,大括號用來表示哈希表。例:

{:name "hello", :age 18}

訪問使用鍵的名字為函數名來獲取值,例:

user=> (:name {:name "Hello", :age 18})
"Hello"

集合

小中大括號都用完了腫麼辦,隻好在大括號之前加個"#"來表示。例:

user=> #{ 1 2 3}
#{1 3 2}

集合中的元素不能重複。如果要構建一個set,可以使用sorted-set函數。

user=> (sorted-set 1 2 2 3)
#{1 2 3}

變量綁定

使用def特殊表可以將值綁定到一個全局變量上。
前麵學習的數據結構,現在都可以用來綁定到變量上了。
def可以多次綁定,以最新綁定的值為準。例:

user=> (def a '(1 2 3))
#'user/a

定義函數

作為Lisp的一種方言,函數是一定在生存階段要學懂的必備技能。

我們使用fn特殊表來定義函數,如果作為函數對象,或者叫做lambda表達式,可以不定義名字。

比如,我們定義一個求平方的匿名函數:

user=> (fn [i] (* i i))
#object[user$eval17$fn__18 0x7f284218 "user$eval17$fn__18@7f284218"]

函數沒有什麼特殊的,也可以用def綁定到一個變量上,這就是我們通常定義函數的寫法:

user=> (def sqr (fn [i] (* i i)))
#'user/sqr
user=> (sqr 4)
16

我們也可以使用defn宏來定義函數:

user=> (defn sqr3 [i] (* i i i))
#'user/sqr3
user=> (sqr3 4)
64

通過defn宏,還可以為函數提供說明文檔,然後文檔就可以通過doc函數來看了。例:

user=> (defn succ "return next x" [x] (+ x 1))
#'user/succ
user=> (doc succ)
-------------------------
user/succ
([x])
  return next x
nil

小結

  • Clojure是一門運行在JVM上的Lisp方言。與Lisp還是有一些不同,比如使用[],{},#{}來表示向量、哈希表和集合。比如不支持car, cdr, setq等。
  • Clojure支持通過import宏來引用java包
  • Clojure通過new特殊表可以創建java對象
  • clojure.core/doc函數用來查看幫助文檔
  • def特殊表用於綁定變量
  • fn特殊表用於定義函數,主要用於定義匿名函數
  • defn宏可以定義函數的說明文檔

最後更新:2017-10-31 23:05:25

  上一篇:go  Sigmoid and SVM(support vertical machine)
  下一篇:go  阿裏雲雙11訪談之容器服務