【轉】現實世界的LISP:Clojure語言初探

      網友投稿 1068 2025-03-31

      我在學生時代最喜歡兩門程序設計語言:Scheme和Haskell。Scheme的簡潔靈活和Haskell的純函數世界都給我留下了 深刻印象,因此一直希望能用這樣的語言做一些實際工作。了解到Clojure之后,欣喜地發現它結合了LISP和函數式語言的優點,同時又擁有JVM成熟 的生態圈,雖然年輕,但可以立即在實際項目中應用。

      開始使用Clojure

      使用Clojure最簡便的方法是安裝Leiningen。它是Clojure的項目管理工具,作用類似于Java的Maven。只需要下載名為lein的 Shell腳本,并在命令行中輸入lein repl,Leiningen就會下載Clojure及它所依賴的.jar庫文件,之后就會看到類似下面的提示:

      接著輸入Clojure表達式,便能看到結果:

      Clojure語法

      Clojure是LISP家族的一門語言,這意味著在語法上,Clojure和其他的LISP語言非常相似。例如(+ 1 2)這個Clojure表達式, 在其他編程語言里一般寫為1 + 2。

      LISP 最著名的特性是它的代碼形式和數據表示形式一致,并有一個專用詞homoiconic描述這種特性。例如上面的(+ 1 2),如果看作數據,它就是一個普通的列表。在求值時,Clojure的運行環境會把列表的第一個元素作為操作或函數,而把后面的元素作為參數。如果在運 行時不希望把一個列表作為表達式對待,可以在前面加’,例如’(+ 1 2)。這意味著Clojure程序可以像操作數據一樣修改和生成其他Clojure程序。這樣改變程序結構的程序在LISP語言中稱為宏。宏系統使得 LISP家族的語言具有高度可擴展性。使用者可以用宏來定義更高層的領域特定語言,使得用來描述和解決問題的語言更加接近問題本身的領域。

      與其他LISP語言相比,Clojure提供了更為豐富的原生數據結構,例如下面這幾種。

      【vector】

      這里的a、b、c是Clojure的符號(Symbol)。每個符號代表運行環境里的某個對象,與其他語言里的標識符(Identifier)概念類似。

      【map】

      這里的:a和:b是鍵,而1和2是它們對應的值。:a和:b這樣以:開頭的表達式被稱為關鍵字(Keyword)。它們和符號有些類似,但只代表它們自己, 而不代表任何其他對象。Ruby中也有類似概念(叫做Symbol,注意不要和Clojure的Symbol混淆)。關鍵字通常被用作map的鍵,但也可 以用其他類型做鍵。上面的map也可以寫作{:a 1, :b 2}。逗號“,”沒有實際意義,一般只為提高可讀性。

      【set】

      當然,數據結構是可以嵌套的。Clojure并不要求一個vector、map或set里的元素是同一類型,也就是說下面的數據都是合法的:

      與其他語言類似,不同的數據結構有不同的性能保證和特性。Clojure為數據結構提供了豐富和一致的操作,例如取元素:

      分號;是Clojure的行注釋符,后面的內容會被忽略,這里我用注釋來說明對每一行求值的結果。很容易注意到,從容器中取元素在語法上和函數調用是一致的,因此也可以把一個容器看作一個函數,它能接受一個參數,返回所對應的值。 第二行也可以寫成下面這樣。

      這是在用Keyword做map的key時為了方便而做的特殊處理,不適用于其他情況。

      再例如添加元素:

      注意用conj向數組和列表添加元素時,得到的結果不一樣。因為conj會以最適合具體數據類型的方式操作——對數組來說向末尾添加元素最高效,對列表來說向頭部添加元素最高效。使用這樣的通用函數可以讓我們定義其他高效通用函數。

      前面說到符號可以代表運行環境中的對象。Clojure可以通過下面的語句把一個符號綁定到特定對象上:

      與其他數據一樣,函數也可以綁定到符號上。

      這里(fn [name] …)定義了一個函數,這個函數有一個名為name的參數。這個函數被綁定到say-hello這個名稱上。如果運行(say-hello “James”),則會輸出Hello James。

      Clojure還有一個定義函數的簡便方法。

      除此之外,Clojure還提供了let用于局部綁定,在且僅在(let …)的范圍內a為1,b為2。

      函數式程序設計

      什么樣的語言可以算是函數式語言有很大爭議。一方面,很多人認為LISP家族的語言都是函數式語言,僅是因為在LISP中函數可以像數據一樣被作為參數和返 回值;另一方面,也有不少人認為函數式語言必須把輸入輸出等副作用排除在主程序之外,讓程序的輸出只依賴于輸入。例如Haskell語言通過Monad抽 象機制在理論上達到了這樣的效果。另外Haskell的延遲求值特性對于實現純函數式語言也是必須的。我的一位老師Paul Hudak是Haskell的主要設計者之一,他曾多次說過LISP不是函數式語言。有些人更愿意把LISP稱為符號語言(Symbolic Language),而把LISP代表的編程范型稱為符號式程序設計(Symbolic Programming)。

      Clojure采取了中間路線。既提供了不可變數據結構,鼓勵函數式程序設計,也并不排斥副作用和非函數式的風格,這與Java實現方便的互操作,充分利用Java的強大生態圈非常重要。另外Clojure語言本身是默認立即求值的,但它也支持延遲求值的數據結構。

      提供不可變的核心數據結構使得Clojure與傳統LISP比更加偏向于函數式語言,函數的參數在邏輯上完全可以被作為值(而不是引用)對待,不會被函數改 變,而結果在邏輯上也是一個新的值。Clojure并不像Haskell一樣對I/O做限制。函數體中的I/O不會反映到函數的類型上。

      高效的不可變數據結構

      前面說過Clojure的核心數據結構是不可變的,它通過提供不可變的數據結構來鼓勵函數式編程。很多習慣于用傳統過程式語言或面向對象語言的朋友不是很理解這一點。舉個簡單的例子,當你向數組的末尾加入一個元素時:

      在邏輯上,一個包含這個新元素和所有舊元素的新數組被創建;

      原來的數組(這不包含這個新元素)仍然可以被訪問;

      這個操作在時間復雜度上的保證和對應的可變數據結構是一致的(對于在數組末尾增加元素來說,平均復雜度應該接近于O(1))。

      要同時滿足以上三個條件 ,顯然Clojure不可能用類似于C/C++的方法實現數組。事實上,Clojure提供的大部分數據結構都是用樹結構實現的。在原有對象基礎上構造新 對象時,只需要復制必須改變的節點。數據可以被作為不可變的值對待,而同時各種操作又能相對比較高效地實現。

      動態語言

      LISP語言是動態語言的先驅。Clojure自然也是一門動態語言。所謂動態,體現在很多方面。首先Clojure使用動態類型系統,每個Symbol所指代的 值的類型是在運行時確定的。對一個函數來說,它只關心參數能接受某些操作,并不對具體類型做限制。例如(fn[a b](+ a b))這個函數,它只要求a和b必須能作為+的參數,而并不對其類型做限制。這樣的動態類型系統稱為Duck Typing,來源于美國詩人James Whitcomb Riley的話:

      當我看到一只像鴨子一樣游泳、像鴨子一樣叫的鳥時,我就認為它是一只鴨子。

      動態也體現在Symbol的綁定上。假設有一個函數some-op是這樣的:

      其中send-email是一個用來發送郵件的函數。它的單元測試中可能有下面的代碼:

      很顯然,我們不能每運行一次測試都真的發送郵件,因此用binding把send-email動態綁定到一個空函數上來避免實際的效果。在測試中,動態綁定還可以用來驗證特定函數的參數和結果。這樣的特性使得Clojure的程序對測試非常友好,對開發大型系統很有幫助。

      與平臺的結合

      目前Clojure存在多個平臺的實現,包括JVM、CLR和JavaScript。為了充分利用各宿主平臺的優勢,Clojure的不同實現除了基本語法 一致之外,并不特別注意可移植性。與其他追求平臺獨立性的語言不同,Clojure更強調和宿主平臺的無縫互操作,因此在Clojure里可以非常容易地 使用第三方Java、CLR或JavaScript庫。這個特點使得Clojure可以充分地利用宿主平臺的成熟生態圈,讓它在發布不久后就顯示出強大的 生命力。

      以JVM上的實現為例,在Clojure里調用Java代碼并不需要類似于FLI(Foreign Language Interface)之類的機制。例如下面的例子:

      簡單幾行就展示了從Clojure調用Java代碼的幾種情況:對象方法、類(靜態)方法/屬性以及創建對象的兩種方式。

      一個簡單實例

      這里展示一個用Clojure做Web開發的簡單例子,希望能讓讀者熟悉用Clojure做現實中的開發所需的知識。在開始一個新項目前,可以用Leiningen生成一個基本的項目框架:

      進入hello目錄后可以看到目錄結構:

      project.clj是Leiningen的項目定義文件,主要包含項目所依賴的庫以及插件的版本信息:

      src目錄是源碼目錄,test目錄是測試目錄。源碼目錄中只有一個文件src/hello/core.clj:

      【轉】現實世界的LISP:Clojure語言初探

      第一行(ns hello.core)聲明了這個文件是模塊hello.core。與Java、Python類似,Clojure的模塊名稱和文件名是存在對應關系的。 (defn foo后面有一個字符串”I don’t do a whole lot.”,這是一個可選的doc string,對foo起說明作用。

      接下來,我們把Noir加到項目的依賴列表里。Noir是一個用來開發Web應用的輕量框架。

      然后把src/hello/core.clj的內容改為:

      開頭(ns …)里的(:use)和(:require)把我們用到的庫引入到當前模塊。defpage是Noir定義網頁的方法,它非常像一個函數:輸入是用戶 的請求和參數,返回的是頁面的內容。短短幾行代碼就完成一個簡單的Web應用。下面用Leiningen運行:

      如果你用瀏覽器訪問http://localhost:9999/World,就可以看到Hello World!。

      結語

      受篇幅所限,本文沒有介紹Clojure的兩個重要特色:豐富的并行運算機制,以及LISP語言最重要的特色之一“宏”。

      有人說Clojure是為并行而設計的,然而它最吸引我的還是LISP語言的簡潔、強大和可擴展性,以及Clojure對函數式程序設計的鼓勵。一門程序設計語言可以通過提供更高層及更豐富的抽象來幫助用戶更方便地描述問題和過程。然而這些抽象是基于人本身對問題和過程的理解,不能超越人的認知。例如 Clojure提供了STM(Software Transactional Memory,軟件事務內存)來管理多個線程對共享資源的訪問,它與其他語言中常見的基于鎖的方案相比有很多優點。但如果一個程序員對STM的機制沒有深 入了解就在Clojure程序中隨意使用,很容易造成問題。STM會帶來便利,但沒有降低對使用者在對問題理解方面的要求。

      Fred Brooks在《No Silver Bullet》中說,沒有任何單一技術進步會使得軟件開發效率大幅度提升。真正困難的問題并不是語言造成的,也不會因為某個語言提供了新的抽象而簡單很 多。語言是一個工具,用戶應該對它有合理的期望。好的工程師可以用任何語言實現高質量的軟件,而一個不好的工程師也不會因為使用一門特定的語言在產出上有 很大提高。Clojure不是Silver Bullet,但它能在一定范圍內提高程序設計的效率。你仍然需要自己分析和尋找問題的答案,但它可以讓你在實現解決方案時更高效。學習和熟悉 Clojure采用的各項技術及倡導的編程風格,可以使用戶成為更好的軟件工程師,同樣的技術和方法也可以應用到其他語言的實踐中去。

      容器 數據結構

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Linux之find命令的參數詳解
      下一篇:表格的寬度和高度不能直接調整怎么搞(如何調整表格高度和寬度)
      相關文章
      亚洲Aⅴ无码专区在线观看q| 国产区图片区小说区亚洲区| 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲 | 国产亚洲av片在线观看18女人| 亚洲国产成人无码AV在线影院| 亚洲喷奶水中文字幕电影| 久久亚洲AV无码精品色午夜麻| 国产亚洲福利精品一区| 77777亚洲午夜久久多人| 亚洲欧洲日产国码av系列天堂| 亚洲综合AV在线在线播放| 亚洲午夜福利在线观看| 亚洲精品成人网站在线观看| 亚洲中文字幕无码一区| 久久久久亚洲精品成人网小说| 久久精品国产亚洲AV果冻传媒| 亚洲av日韩av激情亚洲| 久久久婷婷五月亚洲97号色| 久久亚洲AV成人无码| 亚洲国产福利精品一区二区| 亚洲AV无码成人专区| 亚洲性无码一区二区三区| 亚洲成aⅴ人片久青草影院按摩| 亚洲国产精品日韩av不卡在线| 亚洲AV成人精品一区二区三区| 国产成人+综合亚洲+天堂| 亚洲欧洲精品成人久久奇米网| 久久久久国产亚洲AV麻豆| 亚洲春色在线视频| 久久亚洲精品成人AV| 亚洲人成电影青青在线播放| 亚洲а∨天堂久久精品9966| 亚洲风情亚Aⅴ在线发布| 亚洲AV无码一区二三区 | 亚洲丝袜美腿视频| 亚洲午夜久久久久久尤物| 亚洲熟女www一区二区三区| 在线观看亚洲精品专区| 亚洲性日韩精品一区二区三区| 亚洲欧洲日产国码无码久久99 | 精品国产亚洲第一区二区三区|