[譯] JavaScript 的函數式編程是一種反模式


其實 Clojure 更簡單些

寫了幾個月 Clojure 以後我再次開始寫 JavaScript。就在我試着寫一些很普通的東西的時候,我總會想下面這些問題:javascript

「這是 ImmutableJS 變量仍是 JavaScript 變量?」前端

「我如何 map 一個對象而且返回一個對象?」java

「若是它是不可變的,要麼使用 <這種語法> 的 <這個函數>,不然使用 <不一樣的語法和徹底不一樣行爲> 的 <同一個函數的另外一個版本>」react

「一個 React 組件的 state 能夠是一個不可變的 Map 嗎?」android

「引入 lodash 了嗎?」ios

fromJS 而後 <寫代碼> 而後 .toJS()?」git

這些問題彷佛沒什麼必要。但我猜測我已經思考這些問題上百萬次了只是沒有注意到,由於這些都是我知道的。github

當使用 React、Redux、ImmutableJS、lodash、和像 lodash/fp、ramda 這樣的函數式編程庫的任意組合寫 JavaScript 的時候,我以爲沒什麼方法能避免這種思考。npm

我須要一直把下面這些事記在腦海裏:編程

  • lodash 的 API、Immutable 的 API、lodash/fp 的 API、ramda 的 API、還有原生 JS 的 API 或一些組合的 API
  • 處理 JavaScript 數據結構的可變編程技術
  • 處理 Immutable 數據結構的不可變編程技術
  • 使用 Redux 或 React 時,可變的 JavaScript 數據結構的不可變編程

就算我可以記住這些東西,我依然會遇到上面那一堆問題。不可變數據、可變數據和某些狀況下不能改變的可變數據。一些經常使用函數的簽名和返回值也是這樣,幾乎每一行代碼都有不一樣的狀況要考慮。我以爲在 JavaScript 中使用函數式編程技術很棘手。

按照慣例像 Redux 和 React 這種庫須要不可變性。因此即便我不使用 ImmutableJS,我也得記得「這個地方不能改變」。在 JavaScript 中不可變的轉換比它自己的使用更難。我感受這門語言給我前進的道路下了一路坑。此外,JavaScript 沒有像 Object.map 這樣的基本函數。因此像上個月 4300 多萬人同樣,我使用 lodash,它提供大量 JavaScript 自身沒有的函數。不過它的 API 也不是友好支持不可變的。一些函數返回新的數值,而另外一些會更改已經存在的數據。再次強調,花時間來區分它們是很不划算的。事實大概如此,想要處理 JavaScript,我須要瞭解 lodash、它的函數名稱、它的簽名、它的返回值。更糟糕的是,它的「collection 在先, arguments 在後」的方式對函數式編程來講也並不理想。

若是我使用 ramda 或者 lodash/fp 會好一些,能夠很容易地組合函數而且寫出清晰整潔的代碼。可是它不能和 Immutable 數據結構一塊兒使用。我可能仍是要寫一些參數集合在後而其餘時候在前的代碼。我必須知道更多的函數名、簽名、返回值,並引入更多的基本函數。

當我單獨使用 ImmutableJS,一些事變得容易些了。Map.set 返回全新的值。一切都返回全新的值!這就是我想要的。不幸的是,ImmutableJS 也有一些糾結的事情。我不可避免地要處理兩套不一樣的數據結構。因此我不得不清楚 x 是 Immutable 的仍是 JavaScript 的。經過學習其 API 和總體思惟方式,我可使用 Immutable 在 2 秒內知道如何解決問題。當我使用原生 JS 時,我必須跳過該解決方案,用另外一種方式來解決問題。就像 ramda 和 lodash 同樣,有大量的函數須要我瞭解 —— 它們返回什麼、它們的簽名、它們的名稱。我也須要把我所知的全部函數分紅兩類:一類用於 Immutable 的,另外一類用於其它。這每每也會影響我解決問題的方式。我有時會不自主地想到柯里化和組合函數的解決方案。但不能和 ImmutableJS 一塊兒使用。因此我跳過這個解決方案,想一想其餘的。

當我所有想清楚之後,我才能嘗試寫一些代碼。而後我轉移到另外一個文件,作一遍一樣的事情。

JavaScript 中的函數式編程。

反模式的可視化。

我已孤立無援,而且把 JavaScript 的函數式編程稱爲一種反模式。這是一條迷人之路卻將我引入迷宮。它彷佛解決了一些問題,最終卻創造了更多的問題。重點是這些問題彷佛沒有更高層次的解決方案能避免我一次有又一次地處理問題。

這件事的長期成本是什麼?

我沒有確切的數字,但我敢說若是沒必要去想「在這裏我能夠用什麼函數?」和「我能否改變這個變量」這樣的問題,我能夠更高效地開發。這些問題對我想要解決的問題或者我想要增長的功能沒有任何意義。它們是語言自己形成的。我能想到避免這個問題的惟一辦法就是在路的起點就不要走下去 —— 不要使用 ImmutableJS 、ImmutableJS 數據結構、Redux/React 概念中的不可變數據,以及 ramda 表達式和 lodash。總之就是寫 JavaScript 不要使用函數式編程技術,它看似不是什麼好的解決方案。

若是你肯定並贊成我所說的(若是不一樣意,也很好),那麼我認爲值得花 5 分鐘或一天甚至一週時間來考慮:保持在 JavaScript 路子上相比用一個不一樣的東西取代,耗費的長期成本是什麼?

這個所謂不一樣的東西對於我來講就是 Clojurescript。它是一門像 ES6 同樣的 「compile-to-JS」 語言。大致上說,它是一種使用不一樣語法的 JavaScript。它的底層是被設計成用於函數式編程的語言,操做不可變的數據結構。對我來講,它比 JavaScript 更容易,更有前途。

Clojure/Clojurescript 是什麼?

Clojurescript 相似 Clojure,除了它的宿主語言是 JavaScript 而不是 Java。它們的語法徹底相同:若是你學 Clojurescript,其實你就在學 Clojure。這意味着若是你瞭解了 Clojurescript,你就能夠寫 JavaScript 和 Java。「30 億的設備上運行着 Java」;我很是肯定其餘設備上運行着 JavaScript。

和 JavaScript 同樣,Clojure 和 Clojurescript 也是動態類型的。你能夠 100% 地使用 Clojurescript 語言用 Node 寫服務端的全棧應用。與單獨編譯成 JavaScript 的語言不一樣,你也能夠選擇寫一個基於 Java 的 servrer 來支持多線程。

做爲一個普通的 JavaScript/Node 開發者,學習這門語言及其生態系統對我來講並不困難。

是什麼使得 Clojurescript 更簡單?

在編輯器中執行任意你想要執行的代碼。

  1. 你能夠在編輯器中一鍵執行任何代碼。 的確如此,你能夠在編輯器中輸入任何你想寫的代碼,選中它(或者把光標放在上面)而後運行並查看結果。你能夠定義函數,而後用你想用的參數調用它。你能夠在應用運行的時候作這些事。因此,若是你不知道一些東西如何運做,你能夠在你的編輯器的 REPL 裏求值,看看會發生什麼。
  2. 函數能夠做用於數組和對象。 Map、reduce、filter 等對數組和對象的做用都相同。設計就是如此。咱們毋須再糾結於 map 對數組和對象做用的不一樣之處。
  3. 不可變的數據結構。 全部 Clojurescript 數據結構都是不可變的。所以你不再必糾結一些東西是否可變了。你也不須要切換編程範式,從可變到不可變。你徹底在不可變數據結構的領地上。
  4. 一些基本函數是語言自己包含的。 像 map、filter、reduce、compose 和不少其餘函數都是核心語言的一部分,不須要外界引入。所以你的腦子裏沒必要記着 4 種不一樣版本的「map」了(Array.map、lodash.map、ramda.map、Immutable.map)。你只須要知道一個。
  5. 它很簡潔。 相對於其餘任何編程語言,它只須要短短几行的代碼就能表達你的想法。(一般少得多)
  6. 函數式編程。 Clojurescript 是一門完全的函數式編程語言 —— 支持隱式返回聲明、函數是一等公民、lambda 表達式等等。
  7. 使用 JavaScript 中所需的任何內容。 你可使用 JavaScript 的一切以及它的生態系統,從 console.log 到 npm 庫均可以。
  8. 性能。 Clojurescript 使用 Google Closure 編譯器來優化輸出的 JavaScript。Bundle 體積小到極致。用於生產的打包過程不須要從設置優化到 :advanced 的複雜配置。
  9. 可讀的庫代碼。 有時候瞭解「這個庫的功能是幹嗎的?」頗有用。當我使用 JavaScript 中的「跳轉到定義處」,我一般都會看到被壓縮或錯位的源代碼。Clojure 和 Clojurescript 的庫都直接被顯示成寫出來的樣子,所以不需離開你的編輯器去看一些東西如何工做就很簡單,由於你能夠直接閱讀源碼。
  10. 是一種 LISP 方言。 很難列舉出這方面的好處,由於太多了。我喜歡的一點是它的公式化,(有這麼一種模式能夠依靠)代碼是用語言的數據結構來表達的。(這使得元編程很容易)。Clojure 不一樣於 LISP 由於它並非 100% 的 ()。它的代碼和數據結構中可使用 []{},就像大多數編程語言那樣。
  11. 元編程。 Clojurescript 容許你編寫生成代碼的代碼。這一點有我不想掩蓋的巨大內涵。其中之一是你能夠高效地擴展語言自己。這是一個出自 Clojure for the Brave and True 的例子:
(defmacro infix
  [infixed]
  (list (second infixed) (first infixed) (last infixed)))
(infix (1 + 1))
=> 2
(macroexpand '(infix (1 + 1))) => (+ 1 1) ; 這個宏把它傳入 Clojure,Clojure 能夠正確執行,由於是 Clojure 的原生語法。複製代碼

爲何它並不流行?

既然說它這麼棒,可它怎麼不上天呢?有人指出它已經很流行了,它只是不如 lodash、React、Redux 等等那麼流行而已。但既然它更好,不該該和它們同樣流行嗎?爲何偏心函數式編程、不可變性和 React 的 JS 開發者尚未遷移到 Clojurescript?

由於缺乏工做機會嗎? Clojure 能夠編譯成 JavaScript 和 Java。它實際上也能夠編譯成 C#。所以大量的 JavaScript 工做均可以看成 Clojurescript 工做。它是一種函數式語言,用於爲全部編譯目標完成全部的任務。先不論它的價值如何體現,2017 StackOverflow 的調查代表 Clojure 開發者的薪資水平是全部語言中全球平均最高的

由於 JS 開發者很懶嗎? 並非。正如我在上面所展現的,咱們作了大量的工做。有個詞叫 JavaScript 疲勞,你可能已經據說過了。

咱們很抗拒,不想學點新東西嗎? 並非。 咱們已經因採用新技術而臭名昭著。

由於缺少熟悉的框架和工具嗎? 這感受上多是個緣由,但 Javascript 中有的東西, Clojurescript 都有與之對應的: re-frame 對應 Redux、reagent 對應 React、figwheel 對應 Webpack/熱加載、leiningen 對應 yarn/npm、Clojurescript 對應 Underscore/Lodash。

是由於括號的問題使得這門語言太難寫了嗎? 這方面也許談的還不夠多,但咱們沒必要本身來區分圓括號方括號 。基本上,Parinfer 使得 Clojure 成爲了空格語言。

由於在工做中很難使用? 多是吧。它是一種新技術,就像 React 和 Redux 曾經那樣,在某些時候也是很難推廣的。即便也沒什麼技術限制 ——  Clojurescript 集成到現有代碼庫和集成 React 的方式是相似的。你能夠把 Clojurescript 加入到已經存在的代碼庫中,每次重寫一個文件的舊代碼,新代碼依然能夠和未更改的舊代碼交互。

沒有足夠受歡迎? 很不幸,我想這就是它的緣由。我使用 JavaScript 一部分緣由就是它擁有龐大的社區。Clojurescript 過小衆了。我使用 React 的部分緣由是它是由 Facebook 維護的。而 Clojure 的維護者是花大量時間思考的留着長髮的傢伙

有數量上的劣勢,我認了。但「人多勢衆」否決了全部其餘可能的因素。

假設有一條路通向 100 美圓,它很不受歡迎,而另外一條路通向 10 美圓,它極其受歡迎,我會選擇受歡迎的那條路嗎?

恩,也許會的吧!那裏有成功的先例。它必定比另外一條路安全,由於更多的人選擇了它。他們必定不會遇到什麼可怕的事。而另外一條路聽起來美好,但我肯定那必定是個陷阱。若是它像看起來那麼美好,那麼它就是最受歡迎的那條路了。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索