Clojure如何保證函數式編程的純度(purely functional programming)?

事實上,大部分編程語言不可能是純函數式編程,因為副作用和外部狀態的依賴是不可完全避免的,但副作用和外部狀態的以來是可能導致程序進入危險狀態的,那麼在Clojure中是如何來儘可能保證函數的純度的呢(purity)?看到這樣一個解釋,但不是很理解,特來求教!!

Clojure has two ways of maintaining functional purity as much as possible while still allowing a developer to easily do everything they need.

? Side effects are explicit, and the exception rather than the rule. They are simple to add, when necessary, but they stand out from the natural flow of the language.

This ensures that developers are precisely aware of when and why they occur and

what their precise effects are.

? All program state is contained in thread-safe structures, backed by Clojure』s

thoughtfully planned inventory of concurrency-mangement features. This ensures

that with an absolute minimum of effort, program state is always safe and

consistent. Updates to state are explicit and atomic and clearly identifiable.


你都用 lisp 了還關心純度?


首先簡短的回答是 Clojure 沒有強制要求你要保證函數的 purity,你需要按照實際需要自行考慮是否要使用不可變還是有副作用的數據結構

昨天和一個朋友看完君名後半夜在大馬路上聊天,就談到了 PL 的一些特性造成的社區分裂問題。

Python 分成了 2 和 3 的陣營

Rust 開始有人給它寫 GC

而頂級 Java 項目上開始大量使用 off-heap,即跳過 GC 手動管理內存

至於,Haskell 分成了學術派和工程派在相互撕逼

我當時的回應是:感覺對於 Clojure 來說這類問題不太容易發生

當然這裡有個前提,就是保證以 JVM 實現的為主導。

扯遠了

Clojure 作為基於 JVM 的 Lisp 方言,最初設計思想就是想借用 Java 完善的生態圈,成為解決實際問題的有力工具。Clojure 保留了一些 FP 的規則,比如避免副作用。其真正目的是為了讓開發人員盡量避免其他範式容易踩的坑,比如共享變數造成的競爭可用 swap! atomic 解決。

如果某個要求一定要有副作用怎麼辦?

那就引入副作用吧 (def global-counter (atomic 0))。

一個設計之初就用來解決實際工程問題的 PL 會給開發人員提供各種選擇,而不是強制要求一定要尊崇某種特定形式,除非是為了引入某些重要特性。

最近在折騰 Rust 語言。Rust 有一個出了名的特性就是它可以在編譯時幫你找出大部分常見的內存管理和並行開發相關的問題,慣有「通過編譯基本程序就能跑對」的名稱。而代價就是要仔細考慮每個數據變數的流向,流動方式(借用引用還是直接把所有權交給某個函數),數據的生命周期以及可變還是不可變。不得不說這樣寫起來的確相當痛苦,但是能在獲得接近 C/C++ 性能的前提下不用手動分析解決這些問題。

即使是這樣的PL,還是提供了 Rc,Arc,RefCell 等等標準庫類用於在運行時引用計數的特性,代價就是相比較之下損失一些運行時性能。而針對內存安全問題,你可以使用 unsafe 語法糖手動在運行時管理內存。

前面提到的 Haskell,大概就能回答如果 Clojure 保證了函數的 purity 後會發生什麼事情吧


分清了不可變數據和可變狀態,可變狀態用 atom 類型明確出來。副作用函數有意識用嘆號區分,但看上去 println 本身就沒遵守。不好說純度了。


限制非pure,但不保證pure,也沒必要完全保證。

clj嘗試保持的是平衡,是找到代碼美學和效能美學的平衡點,而不是FP的純度。所以人家說的是as much as possible while still allowing a developer to easily do everything they need.

說個偏題的(不針對題主,僅借題發揮),作為一個使用clj工作四年,不遺餘力推行FP在國內發展,也接觸過很多其他語言的人,我現在挺煩一些沉迷於FP概念的狂熱者的。

他們看到個什麼東西就要討論pure不pure,然後擺出一副你不pure你就不好的干架態勢來,更有甚者,把自己熟悉的某些語言的某些語言特性拿出來,強行劃歸到定義FP的必要條件里,你要沒這些特性,你就不Pure,你就是不好的。他們與N年前OOP的無條件推崇者是同一批人,說不定以後又會是其他概念的狂熱信徒。

Purity的本質目的是程序執行的安全性(規避bug的能力)與代碼的工整性(解耦能力),扯開這些其他都是空談。在特定的場合下,使用非pure的東西也很可能是權衡之後的最佳選擇。

說句可能會讓很多人傷心的,FP,甚至其牽連的整個Programming Language Theory,從一開始就是為了服務於工程——而非做為「定義可計算系統的一種符號表達的純粹理論」——而存在的,所以脫離工程上的實際問題,去爭論purity與否是好是壞,去嘗試圍繞這些概念構建評判的,只能說連FP的目的都沒真正明白,其爭論本身,多數也沒什麼意義。


clojure中不純的函數聲明會加一個!。但這只是一種約定,不是必須的。clojure沒有辦法嚴格區分純函數還是不純函數


推薦閱讀:

關於函數式編程的思考?
如何理解下面這段Haskell代碼?
參加 2017 年函數式編程聚會是什麼感受?
微軟和 Haskell 之間有什麼關係?
函數式編程所倡導使用的「不可變數據結構」如何保證性能?

TAG:函數式編程 | Clojure |