首頁 > web前端 > js教程 > 與ramda.js的實踐功能編程

與ramda.js的實踐功能編程

William Shakespeare
發布: 2025-02-17 10:40:10
原創
305 人瀏覽過

Hands-on Functional Programming with Ramda.js

本文經 Yaphi Berhanu、Vildan Softic、Jani Hartikainen 和 Dan Prince 審核。感謝所有 SitePoint 的同行評審員,使 SitePoint 內容達到最佳狀態!

JavaScript 的魅力之一在於其函數式編程特性。從一開始,函數就是 JavaScript 世界中的一等公民。這使得編寫優雅且富有表現力的代碼成為可能,這些代碼可以以多種方式輕鬆組合在一起。然而,僅僅具備進行函數式編程的能力並不能自動實現函數式編程。 Ramda.js 是一個非常流行的庫(在 GitHub 上擁有超過 4k 星),我們可以使用它來幫助我們開始使用 JavaScript 進行函數式編程。

要點

  • Ramda.js 通過確保函數不改變其輸入數據來增強 JavaScript 的函數式編程能力,從而提高可預測性和易於測試性。
  • Ramda.js 的主要特性包括函數的自動柯里化和對不變性的強烈強調,這使得更容易構建沒有副作用的函數式管道。
  • 該庫適用於前端和 Node.js 環境,可以與其他 JavaScript 庫或框架無縫集成。
  • Ramda.js 提供了諸如 R.mapR.propR.curry 之類的實用方法和函數,簡化了數據結構的操作,並增強了代碼的可讀性和可維護性。
  • 儘管 Ramda.js 具有諸多優點,但它並非萬能的解決方案;開發人員在決定是否使用它時,應該考慮具體的項目需求和團隊對函數式編程的熟悉程度。

入門

為了充分利用 Ramda.js,我們應該通過創建一個小型 Node.js 項目來熟悉它的優點。我們可以通過 Node 包管理器 (npm) 簡單地安裝它。

npm install ramda
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

通常,我們將簡單地將庫的功能導入到命名空間 R 中。這樣,對 Ramda 方法的所有調用都將具有 R. 前綴。

var R = require('ramda');
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

當然,沒有什麼可以阻止我們在前端代碼中使用 Ramda.js。在瀏覽器中,我們只需要包含指向庫副本的正確路徑即可。這可能與以下 HTML 代碼片段一樣簡單。

<🎜>
登入後複製
登入後複製
登入後複製
登入後複製

Ramda.js 不使用任何 DOM 或 Node.js 特定的特性。它只是一個語言庫/擴展,並且建立在 JavaScript 運行時(如 ECMAScript 5 中標準化的那樣)已經公開的結構和算法之上。準備深入研究嗎?讓我們看看一些功能的實際應用!

概念

函數式編程中最重要的概念是純函數的概念。純函數是冪等的,不會改變任何狀態。從數學角度來看,這很有意義,因為像 sin(x) 這樣的函數似乎非常自然,並且不依賴於任何外部狀態。除了純函數之外,我們還希望擁有單參數函數。它們是最原始的。零參數函數通常表示將更改外部狀態,因此不是純函數。但在像 JavaScript 這樣的語言中,我們通常會有多個參數的函數。

柯里化

具有高階函數(即可以將函數作為輸入並發出函數作為輸出的函數)的能力與閉包(捕獲局部變量)相結合,為我們提供了一種很好的方法:柯里化。柯里化是一個過程,其中一個具有多個(假設為 n 個)參數的函數被轉換為一個具有單個參數的函數,該函數返回另一個具有單個參數的函數。這將持續進行,直到收集所有必需的參數。假設我們想使用 Ramda.js 輔助函數來編寫一個單參數包裝器,該包裝器測試其參數是否為字符串。以下代碼將完成這項工作。

npm install ramda
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

使用柯里化可以更容易地做到這一點。由於 R.is 是 Ramda.js 的一部分,因此如果我們提供的參數少於函數所需要的參數,該庫將自動返回一個柯里化函數:

var R = require('ramda');
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

這更具表現力。由於我們使用單個參數調用了 R.is,因此我們得到了一個函數。在第二次調用(記住,原始函數調用需要兩個參數)中,我們獲得了結果。但是,如果我們一開始沒有使用 Ramda.js 的輔助函數呢?讓我們假設我們已經在代碼中的某個地方定義了以下函數:

<🎜>
登入後複製
登入後複製
登入後複製
登入後複製

這是完整的二階多項式。它有四個參數,允許所有可能的值。但是通常,我們只想為一組固定的參數 a、b 和 c 更改 x。讓我們看看如何使用 Ramda.js 來轉換它:

function isString (test) {
    return R.is(String, test);
}

var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

同樣,我們能夠簡單地使用參數求值來為特定子集創建別名。例如,方程 x - 1 可以通過以下方式獲得:

var isString = R.is(String);
var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

在參數數量不是由我們函數的參數給定的情況下,我們需要使用 curryN 並顯式指定參數數量。柯里化是 Ramda.js 的核心,但如果沒有更多內容,該庫似乎就不那麼有趣了。函數式編程中另一個重要的概念是不變性。

不變結構

防止函數更改狀態的最簡單方法是只使用不能更改的數據結構。對於簡單的對象,我們需要只讀訪問器,例如:

var quadratic = (a, b, c, x) => x * x * a + x * b + c;
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
登入後複製
登入後複製
登入後複製

是不允許的。除了聲明只讀屬性外,我們還可以將它們轉換為 getter 函數:

var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c);
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> 4
登入後複製
登入後複製

現在這已經好多了,但是,對象仍然可以更改。這意味著有人可以添加 getX 函數的自定義定義,例如:

npm install ramda
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

實現不變性的最佳方法是使用 Object.freeze。結合 const 關鍵字,我們可以引入一個不能更改的不變變量。

var R = require('ramda');
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

另一個例子涉及列表。將元素添加到不可變列表需要復制原始列表並將新元素添加到末尾。當然,我們也可以使用原始對象的不變性知識來優化實現。這樣,我們可以用簡單的引用替換副本。本質上,這可能會變成一種鍊錶。我們應該意識到,標準 JavaScript 數組是可變的,因此需要復制以確保正確性。諸如 append() 之類的方法在 JavaScript 數組上工作並返回此類數組。該操作是冪等的;如果我們多次使用相同的參數調用該函數,我們將始終獲得相同的結果。

<🎜>
登入後複製
登入後複製
登入後複製
登入後複製

還有一個 remove 方法,它返回給定數組,但不包含指定的條目。它的工作原理如下:

function isString (test) {
    return R.is(String, test);
}

var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

由於它具有靈活的參數數量,因此我們需要前面提到的 curryN 函數來應用柯里化。還有一些有用的通用輔助函數可用。

實用方法

所有輔助函數最重要的概念是參數是有序的以方便柯里化。參數越頻繁地被更改,它就越不可能位於其他參數之前。

sum() 和 range()

Ramda.js 中當然可以找到通常的函數,例如 sum 和 range:

var isString = R.is(String);
var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

對於 range() 輔助函數,我們可以使用柯里化創建一個包裝器:

var quadratic = (a, b, c, x) => x * x * a + x * b + c;
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
登入後複製
登入後複製
登入後複製

如果我們想用固定的(獨占)最大值來包裝它呢? Ramda.js 通過使用由 R.__ 表示的特殊參數來滿足我們的需求:

var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c);
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> 4
登入後複製
登入後複製

map()

此外,Ramda.js 嘗試使用“更好”的解決方案來提供 JavaScript 核心函數(例如 Array.prototype.map)的替代方案。 “更好”的解決方案具有不同的參數順序和開箱即用的柯里化。對於 map 函數,如下所示:

var xOffset = quadratic(0, 1, -1);
xOffset(0); //=> -1
xOffset(1); //=> 0
登入後複製

prop()

另一個有用的實用程序是 prop 函數,它嘗試獲取指定屬性的值。如果給定的屬性不存在,則返回 undefined。如果值確實是 undefined,這可能會模棱兩可,但在實踐中我們很少會關心。

var position = {
    x: 5,
    y: 9
};
position.x = 10; // works!
登入後複製

zipWith()

如果前面介紹的方法沒有讓你相信 Ramda.js 可能提供一些有用的東西,那麼接下來的方法可能更有趣。這次我們不會討論一個具體的例子,而是看看任意選擇的場景。假設我們有兩個列表,我們想將它們連接起來。使用 zip 函數實際上非常簡單。但是,通常的結果(元素數組,它們本身是雙值數組)可能不是我們想要的結果。這就是 zipWith 函數發揮作用的地方。它使用任意函數將值映射到單個值。

var position = (function (x, y) {
    return {
        getX: () => { return x; },
        getY: () => { return y; }
    };
})(5, 9);
position.getX() = 10; // does not work!
登入後複製

類似地,我們可以為向量引入點積:

npm install ramda
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

我們通過乘法壓縮兩個數組(產生 [1, 4, 9]),並將結果傳遞給 sum 函數。無論如何,使用可枚舉對像是重要的主題。 Ramda.js 提供了許多有用的輔助函數也就不足為奇了。我們已經介紹了 R.map 來將函數應用於每個元素。類似地,有一些輔助函數可以減少元素的數量。可以通過最通用的 filter 函數(產生另一個數組)或通過 reduce 函數產生單個值。

chain()

對數組的操作帶有一些有用的輔助函數。例如,使用 chain,我們可以輕鬆合併數組。假設我們有一個函數 primeFactorization,它使用數字作為輸入並給出帶有素數因子的數組作為輸出,我們可以將應用該函數的結果與一組數字組合如下:

var R = require('ramda');
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

一個實際的例子

到目前為止,一切都很順利。現在最大的問題是:通過使用 Ramda.js 引入的這些概念,我們在日常工作中有哪些好處?讓我們假設我們有以下(已經非常好看的)代碼片段。

<🎜>
登入後複製
登入後複製
登入後複製
登入後複製

如何使用 Ramda.js 使其更具可讀性?好吧,第一行已經足夠好了。第二行已經很混亂了。我們真正想要的是提取提供的參數的 posts 屬性。最後,我們有一個有點混亂的第三行。在這裡,我們嘗試迭代所有帖子(由參數提供)。同樣,它的唯一目的是提取特定屬性。以下解決方案如何?

function isString (test) {
    return R.is(String, test);
}

var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

由於 Ramda.js 賦能的函數式編程,這可能是關於可讀性的最佳解決方案。但是,我們應該注意,ECMAScript 6 中引入的“胖箭頭”語法也導致了非常簡潔、易讀的代碼:

var isString = R.is(String);
var result = isString('foo'); //=> true
登入後複製
登入後複製
登入後複製

這幾乎同樣易於閱讀,無需任何 Ramda.js 知識。此外,我們減少了抽象的數量——這只會對性能和可維護性有利。

透鏡

最後,我們還應該討論有用的對象輔助函數。這裡值得一提的是 lens 函數。透鏡是一個特殊的對象,可以與對像或數組一起傳遞給某些 Ramda.js 函數。它允許這些函數分別從對像或數組的特定屬性或索引檢索或轉換數據。假設我們有一個具有兩個鍵 x 和 y 的對象——就像文章開頭給出的不變性示例一樣。與其將對象包裝在另一個具有 getter 和 setter 方法的對像中,我們可以創建一個透鏡來“關注”感興趣的屬性。要創建一個訪問對象的 x 屬性的透鏡,我們可以執行以下操作:

var quadratic = (a, b, c, x) => x * x * a + x * b + c;
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
登入後複製
登入後複製
登入後複製

雖然 prop 是一個標準的 getter(這已經介紹過了),但 assoc 是一個 setter 函數(三個值語法:鍵、值、對象)。現在我們可以使用 Ramda.js 中的函數來訪問此透鏡定義的屬性。

npm install ramda
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

請注意,該操作不會觸及給定的 position 對象(無論我們是否凍結它)。應該注意的是,set 只是 over 的一個特例,它類似但採用函數而不是任意值。然後將使用該函數來轉換值。例如,以下調用將 x 坐標乘以 3:

var R = require('ramda');
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

Ramda.js、lodash 或其他?

一個合理的問題當然是為什麼選擇 Ramda.js——為什麼我們不使用 lodash 或其他東西呢?當然,有人可能會爭辯說 Ramda.js 更新,因此必須更好,但這與事實相去甚遠。事實是,Ramda.js 是考慮到函數式原則而構建的——在參數放置和選擇方面採用了新的方法(對於 JavaScript 庫)。例如,Ramda.js 中的列表迭代器默認只傳遞項目,而不是列表。另一方面,其他庫(如 lodash)的標準是將項目和索引傳遞給回調函數。這似乎是一個細微的問題,但它阻止你使用方便的內置函數,如 parseInt()(它帶有一個可選的第二個參數),而使用 Ramda.js,這可以很好地工作。最終,選擇什麼取決於具體的要求或團隊的經驗和/或知識,但肯定有一些很好的理由讓 Ramda.js 受到應有的關注。

進一步閱讀

  • 高階函數
  • 為什麼柯里化有幫助
  • 不變性
  • 為什麼選擇 Ramda?
  • Ramda 文檔
  • 使用 Ramda.js 進行函數式編程

結論

函數式編程不應被視為靈丹妙藥。相反,它應該被視為我們現有工具箱的一個自然補充,它為我們提供了更高的可組合性、更大的靈活性和更高的容錯性/魯棒性。現代 JavaScript 庫已經嘗試採用一些函數式概念來利用這些優勢。 Ramda.js 是一個強大的工具,可以擴展你自己的函數式實用程序庫。你對函數式編程的看法是什麼?你認為它在哪些方面表現出色?請在評論中告訴我!

關於使用 Ramda 進行函數式編程的常見問題解答 (FAQs)

使用 Ramda 進行函數式編程的主要優勢是什麼?

Ramda 是一個針對 JavaScript 程序員的實用函數式庫。它專為函數式編程風格而設計,這使得創建函數式管道更容易,並且永遠不會改變用戶數據。使用 Ramda 的主要優勢在於它強調不變性和無副作用函數。這意味著函數不會改變其輸入數據,使你的代碼更易於預測和測試。 Ramda 的函數是自動柯里化的,這使你可以輕鬆地從舊函數構建新函數,而不會迷失在一堆括號或回調地獄中。

Ramda 與其他 JavaScript 庫有何不同?

與其他 JavaScript 庫不同,Ramda 旨在支持函數式編程並且不會改變數據。它提供了一組默認情況下是柯里化的實用函數,這意味著它們旨在一起使用以創建新函數。這與 Underscore 或 Lodash 等庫有很大不同,這些庫默認情況下不是柯里化的,並且通常需要你編寫額外的代碼才能獲得相同的結果。 Ramda 的 API 也更一致且更易於使用,重點是簡單性和可讀性。

我可以將 Ramda 與其他 JavaScript 庫或框架一起使用嗎?

是的,Ramda 可以與其他 JavaScript 庫和框架一起使用。它是一個獨立的庫,不依賴於任何其他庫或框架,並且不會修改 JavaScript 對象原型。這使得它可以安全地與其他庫或框架一起使用,而無需擔心衝突或意外副作用。無論你使用的是 jQuery、React、Angular、Vue 還是任何其他庫或框架,你都可以使用 Ramda 來幫助編寫更簡潔、更函數式的代碼。

Ramda 適用於函數式編程初學者嗎?

Ramda 是函數式編程新手的一個很棒的工具。它的 API 設計簡單直觀,命名約定清晰一致。文檔也非常詳盡,有很多示例可以幫助你入門。但是,像任何新的工具或範例一樣,都存在學習曲線。可能需要一些時間才能習慣函數式思維,並理解如何有效地使用 Ramda 的函數。但是,隨著練習,你會發現它可以極大地簡化你的代碼,並使代碼更容易理解。

Ramda 如何處理 null 和 undefined 值?

Ramda 將 null 和 undefined 值視為空值,類似於其他函數式編程語言如何處理它們。這意味著你可以安全地將 null 或 undefined 傳遞給 Ramda 的函數,而不會導致錯誤或異常。但是,在將 null 或 undefined 值傳遞給函數之前,始終檢查它們是一個好主意,以避免意外行為。

我可以在 Node.js 環境中使用 Ramda 嗎?

是的,Ramda 可用於 Node.js 環境。它是一個通用的庫,可在瀏覽器和 Node.js 中使用。你可以通過 npm 安裝它,並在你的 Node.js 模塊中像其他任何包一樣需要它。

Ramda 如何處理異步操作?

Ramda 沒有內置的異步操作支持,因為它主要是一個同步庫。但是,你可以將其與支持異步操作的其他庫(如 Promises 或 async/await)一起使用。你也可以在異步函數或 then 回調中使用 Ramda 的函數。

如何為 Ramda 項目做出貢獻?

Ramda 是一個開源項目,歡迎隨時貢獻。你可以通過報告錯誤、建議新功能、改進文檔或提交拉取請求來做出貢獻。在貢獻之前,最好閱讀 Ramda GitHub 頁面上的貢獻指南。

Ramda 是否仍在維護和更新?

是的,Ramda 正在積極維護並定期更新。維護人員致力於使庫保持最新狀態並解決出現的任何問題或錯誤。你可以查看 GitHub 頁面以獲取最新的更新和版本。

我可以將 Ramda 用於商業項目嗎?

是的,Ramda 採用 MIT 許可證授權,這意味著你可以將其用於商業項目。但是,始終閱讀完整的許可協議以了解你的權利和責任是一個好主意。

以上是與ramda.js的實踐功能編程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板