【譯】成爲一名函數式碼農系列之六

關於本文:

原文地址 翻譯地址 譯者:野草

本文發表於前端早讀課【第888期】

往期回顧:

成爲一名函數式碼農系列之一

成爲一名函數式碼農系列之二

成爲一名函數式碼農系列之三

成爲一名函數式碼農系列之四

成爲一名函數式碼農系列之五

下一步

你已經學了函數式編程相關的所有新知識,你可能開始困惑:“然後呢?我如何才能在我的工作中實踐起來?”

這得具體問題具體分析。如果你工作中用的是像Elm或者Haskell這類純函數語言,那這些知識很容易就能運用起來。

如果你只能用某種命令式語言(這種情況很常見),比如JavaScript,你還是能運用大部分我們之前提到的知識點,不過會有很多的限制。

函數式JavaScript

JavaScript擁有很多類函數式的特性。JavaScript沒有純性,但是我們可以設法得到一些不變量和純函數,甚至可以藉助一些庫。

但這並不是理想的解決方法。如果你不得不使用純特性,爲何不直接考慮函數式語言?

不變性(Immutability)

首先要考慮的點就是不變性。ES6新增了一個關鍵詞const,它意味着一旦被賦值,就不能重新設置:

const a = 1;
a = 2; // 拋出類型錯誤的異常 

此處a定義爲常量,也就是說一旦被設定就不能再修改了,這就是爲什麼a = 2會拋出異常錯誤(除了Safari外)。

const的缺陷在於它不夠嚴格,我們來看個例子:

const a = {
    x: 1,
    y: 2
};
a.x = 2; // 沒有異常!
a = {}; // 拋出異常:類型錯誤

注意到a.x = 2沒有拋出異常。用const關鍵詞限制的只有變量a本身,a所指向的對象是可變的。

這很槽糕。本來以爲有了const,JavaScript會更加完善。

那麼問題來了,如何才能在JavaScript中得到不變性。

不幸的是,只有Immutable.js庫能做到。雖然它能保證更好的不可變形,但悲劇的是,它以更像Java的方式寫我們的JavaScript。

柯里化與整合(curring and composition)

之前我們已經學會如何將函數柯里化,舉一個複雜的例子再回顧一下:

const f = a => b => c => d => a + b + c + d

我們得手寫上述柯里化的過程。然後用如下的方式調用:

console.log(f(1)(2)(3)(4)); // 打印出 10

括號之多,連寫Lisp的程序員都要hold不住了。

簡化上述過程的庫很多,我最喜歡用的庫是Ramda

我們用Ramda去改寫上面的例子:

const f = R.curry((a, b, c, d) => a + b + c + d);
console.log(f(1, 2, 3, 4)); // 打印出 10
console.log(f(1, 2)(3, 4)); // 打印出 10
console.log(f(1)(2)(3, 4)); // 打印出 10

函數定義的方法並沒有改進多少,但在調用的時候可以避免寫那麼多括號了。調用f的時候,你想任意指定參數的個數。

我們重寫一下之前的mult5AfterAdd10函數:

const add = R.curry((x, y) => x + y);
const mult5 = value => value * 5;
const mult5AfterAdd10 = R.compose(mult5, add(10));

事實上Ramda提供了很多輔助函數來做些簡單常見的運算,比如R.add以及R.multiply。以上代碼我們還可以簡化:

const mult5AfterAdd10 = R.compose(R.multiply(5), R.add(10));

Map,Filter和Reduce

Ramda也有對應的maoFilter以及reduce函數。在原生JavaScript中,這幾個函數是在Array.prototype對象中的,而在Ramda中它們是柯里化的:

const isOdd = R.flip(R.modulo)(2);
const onlyOdd = R.filter(isOdd);
const isEven = R.complement(isOdd);
const onlyEven = R.filter(isEven);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(onlyEven(numbers)); // 打印出 [2, 4, 6, 8]
console.log(onlyOdd(numbers)); //  打印出 [1, 3, 5, 7]

R.modulo接受2個參數,被除數和除數。

isOdd函數表示一個數除2的餘數。若餘數爲0,則返回false,即不是奇數;若餘數爲1,則返回true,是奇數。用R.filp置換一下R.modulo函數兩個參數順序,使得2作爲除數。

isEven函數是isOdd函數的補集。

onlyOdd函數是由isOdd函數進行斷言的過濾函數。當它傳入最後一個參數,一個數組,它就會被執行。

同理,onlyEven函數是由isEven函數進行斷言的過濾函數。

當我們給函數onlyEvenonlyOdd傳入numbersisEvenisOdd獲得了最後的參數,然後執行最終返回我們期望的數字。

JavaScript缺陷

借用了庫和語言增強工具,JavaScript已經能做那麼多事情了,但它仍然有個致命的缺陷——它是命令式語言,卻想什麼都做。

絕大多數的前端開發不得不使用JavaScript,因爲它是瀏覽器唯一接受的語言。不過,也有很多開發者繞過了這個坑——他們用另一種語言編寫,然後編譯成JavaScript。

CoffeeScript是這類語言中最早的一批。目前,TypeScript已經被Angular2採用。Babel可以將這類語言編譯成JavaScript。

越來越多的開發者在項目中採用這種方式。

但這些語言本質上還是JavaScript,並未有明顯改善。爲何我們大膽嘗試直接使用一門純函數語言,然後轉譯成JavaScript?

Elm

這系列文章,我們已經借用Elm來幫助我們理解函數式編程。

那Elm是什麼?怎麼用?

Elm是一種能編譯成JavaScript的純函數語言,你可以用Elm腳手架搭建一個Web應用。Elm腳手架,全稱爲The Elm Architecture,簡稱TEA,它是Redux的啓蒙者。

Elm程序在運行時不會報錯。

Elm在一些公司中已經投入了應用,比如NoRedLink。Evan Czapliki大神,Elm的作者,目前在這家公司工作(準確地說,他在Prezi工作)。

更多信息參見6 Months of Elm in Production,NoRedInk的Richard Feldman關於Elm的分享。

我要用Elm取代所有JavaScript嗎?

不用,你可以逐步地取代。具體可參考教程How to use Elm at Work

爲什麼要學Elm?

  • 純函數式編程具有約束和釋放雙重特性,它約束你的行爲(通常是避免了給自己挖坑)同時讓你遠離bug和槽糕的設計。因爲所有的Elm程序都要遵循Elm腳手架的規範。
  • 學習函數式編程能讓你成爲更好的程序員。本文涉及到的只是函數式編程的冰山一角。你應該去實踐一下,感受它的魅力,感受它是如何減少你的代碼量以及增加穩定性。
  • JavaScript最初是在10天內倉促地完成的,然後在過去的20多年裏一直在打補丁,直到現在變成了一門有一點點函數式,一些些面向對象,完完全全的命令式編程語言。Elm學習了Haskell社區過去30多年的精華,而Haskell社區也有數十年的數學和計算機工作的沉澱。並且Elm腳手架的設計是Evan在函數響應式編程方面發表的論文結果實現,這幾年來一直不斷改善優化。(Controlling Time and Space 提到了它的設計理念)
  • Elm是爲前端開發人員而生,旨在簡化開發工作。(Let’s Be Mainstream深刻地說明了這一點)

未來展望

我們無法預知未來的趨勢,但至少我們可以做有根據的猜測。以下是我的一些拙見:

能轉譯成JavaScript的這類語言將會有大進展;

存在40多年的函數式編程思想將重新被挖掘出來,用來解決我們目前遇到的複雜問題;

目前的硬件,比如廉價的內存,快速的處理器,使得函數式技術普及成爲可能;

CPU不會變快,但是內核的數量會持續增加;

在複雜項目系統中可變性將成爲最大要害之一。

我之所以寫這系列文章,是因爲我相信函數式編程是未來趨勢,而且過去幾年我學得挺費勁,當然我現在也還在學習中。

我希望我能幫助你們更容易更快地學會這些概念,幫助你們提升技能,然後未來有更好的出路。

即使我預言Elm將會普及的觀點是錯誤的,我敢說函數式編程和Elm語言是未來中的一部分。

我希望你讀完這個系列之後,你對這些概念有了清晰的掌握,對自己也更有信心。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章