天天看點

《Clojure程式設計》——第1章,第1.2節Clojure程式設計快速入門

本節書摘來自異步社群《clojure程式設計》一書中的第1章,第1.2節clojure程式設計快速入門,作者 【美】stuart halloway , aaron bedra,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

1.2 clojure程式設計快速入門

clojure程式設計

要運作clojure及本書的示例代碼,你需要兩件東西。

java運作時。請下載下傳1并安裝java 5或是更高版本。java 6具有顯著的性能提升和更好的異常報告,如果可能就盡量選它吧。

leiningen2。leiningen是一個用于管理依賴項的工具,并且可以基于你的代碼啟動各種任務。在clojure世界中,它是處理這項工作最常用的工具了。

你将會使用leiningen來安裝clojure和本書所有示例代碼的依賴項。如果你已經安裝了leiningen,那就應該已經熟悉相關的基礎知識了。否則,你應該快速的浏覽一下leiningen的github首頁3,在那兒你能找到如何安裝,以及其基本用法的說明。現在還不用急着去學習所有這一切,因為本書會指引你輕松地掌握那些必須的指令。

在閱讀本書期間,請使用與本書示例代碼比對的clojure版本。讀完本書後,你就可以按照“自行建構clojure”中的說明,建構一個最新鮮的clojure版本。

自行建構clojure

你可能希望從源碼建構clojure,以獲得最新的特性和bug修複。這裡是具體做法。

本書的示例代碼會經常更新,與比對clojure目前最新的開發版本。請檢查示例代碼目錄中的readme檔案,裡面有最近一次測試通過時,對應的clojure修訂版本号。

請參閱前言中“下載下傳示例代碼”中說明,下載下傳本書的示例代碼。當你下載下傳了示例代碼後,你還需要使用leiningen擷取它們的依賴項。請在示例代碼的根目錄下執行。

那些依賴項将被下載下傳到本地并放置在适當的位置。為了測試安裝是否正确,你可以進入到放置示例代碼的目錄,并啟動一個clojure的repl(讀取-求值-列印循環)。leiningen包含了一個啟動repl的腳本,它可以連同依賴項一起加載clojure,本書後面部分會用到那些依賴項。

當你成功的啟動了repl,它将會顯示“user=>”對你進行提示。

現在,你已經為“hello world.”做好了準備。

1.2.1 使用repl

來看看如何使用repl,讓我們建立幾個“hello world”的變體。首先,在repl提示符下鍵入(println "hello world")。

第二行的“hello world”,就是repl針對你送出的請求,産生的控制台輸出。

接下來,将你的“hello world”封裝成一個函數,讓它可以通過名字向人問好。

我們來分析一下。

defn定義了一個函數。

hello是這個函數的名稱。

hello函數接受一個參數name。

str是一個函數調用,把由任意參數組成的清單連接配接為一個字元串。

defn、hello、name和str都是符号(symbols),代表了它們各自涉及事物的名稱。在第2.1.2小節“符号”中有關于合法符号的定義。

再看看這行代碼的傳回值:#'user/hello。字首#'表示這個函數是用一個clojure變量(var)來儲存的,其中user是這個函數所在的命名空間(namespace)(就像java的預設包一樣,user是repl的預設命名空間)。你現在還不必為變量和名字空間擔憂,第2.4節“變量、綁定和命名空間”裡有關于它們的讨論。

你現在可以調用hello函數,并傳入你的名字了。

如果你發現repl的狀态令你倍感困惑,最簡單的解決辦法就是直接關閉這個repl(windows下使用ctrl+c,*nix下則是ctrl+d),然後再另外啟動一個。

1.2.2 特殊變量

repl包括幾個有用的特殊變量。當你使用repl時,最近三次求值結果的描述被分别存儲在特殊變量1、2和*3中。這使得進行疊代變的非常容易。下面,讓我們向幾個不同的名字問聲好。

現在,你可以使用那幾個特殊變量,把最近的幾個工作成果組合起來。

如果你在使用repl的過程中犯了錯,你會看到一個java異常。出于簡潔方面的考慮,細節往往被省略了。例如,除以零是不允許的。

這是個顯而易見的問題,但有些時候問題會更加微妙,這時你就需要獲得更詳細的堆棧跟蹤(stack trace)資訊了。最後一個異常被儲存在特殊變量*e中。由于clojure異常就是java異常,是以你能使用pst函數4(print stacktrace)得到堆棧跟蹤資訊。

更多與java互操作方面的内容,參見第9章“極盡java之所能”。如果你的代碼塊實在太大,不便于在repl中逐行敲入,不妨将代碼儲存到一個檔案中,然後通過repl,使用絕對路徑或是相對路徑(相對于啟動repl的路徑)來加載這個檔案。

; 儲存一些東西到temp.clj中, 然後執行...

repl是一個美妙的場所,在這裡你可以嘗試各種想法并立即獲得回報。為達到最佳效果,閱讀本書時,請務必保持随時都開啟着repl。

1.2.3 添加共享狀态

上一節中的hello函數是“純粹的”,也就是說,它不會産生任何副作用。純函數易于開發、測試,并易于了解,你應該優先選擇它們來處理任務。

可是,大多數程式擁有共享狀态,并且需要使用非純粹的函數來管理這些共享狀态。讓我們對hello函數進行擴充,使其能夠追蹤過往訪客的足迹。首先,你需要一種資料結構來追蹤訪客。集合就非常合适。

{}是空集合的字面表示法。接下來,你需要conj函數。

conj是conjoin(連接配接)的縮寫,它會建立一個含有新增項的集合。将元素連接配接到集合,就好像是建立了一個新的集合。

現在你可以建立新的集合了,但你還需要某種方法來對目前訪客的集合保持跟蹤。為此,clojure提供了幾種引用類型。最基本的引用類型是原子。

你可以使用def來為你的原子命名。

def有點像defn,但更為通用。def既能定義函數,又能定義資料。下面使用atom建立一個原子,并用def将這個原子綁定到名稱visitors上。

要更新一個引用,你需要使用諸如swap!這樣的函數。

swap!會對拿引用r去調用update-fn,并根據需要傳遞其他可選的參數。下面試一下用conj作為更新函數,把一個訪客swap!進入到訪客集合中。

原子隻是clojure的幾種引用類型之一。選擇恰當的引用類型時,需要格外小心仔細(相關讨論參見第5章“狀态”)。

你可以在任何時候使用deref或者它的縮寫@号來提取引用内部的值。

現在,是時候建立這個更加複雜的新版hello了。

下一步,檢查一下看能否在記憶體中正确地追蹤了。

你的訪客清單十有八九與此處顯示的不同。這就是狀态搗的亂!結果是否會有差别,取決于事情何時發生。你還可以據此推論出一個函數是否管理着本地資訊。對狀态進行推斷,需要對其演變曆史有着充分的認識。

隻要可能,就應該極力避免狀态。但是當你确實需要它的時候,通過使用諸如原子這樣的引用類型,就能讓狀态保持完整以及可控。原子(和所有其他的clojure引用類型)對于多個線程和多個處理器都是安全的。更棒的是,獲得這種安全性無需借助聲名狼藉的鎖定機制,那實在是太讓人抓狂了。

至此,你應該已經能很舒暢的在repl中錄入那些較短的代碼了。其實那些較長的代碼也沒有太多不同,你同樣可以在repl中加載并運作大量有成百上千行代碼的clojure庫。下面讓我們來探索一下吧。

4 pst函數僅适用于clojure1.3.0及更高版本。